PowerShell for Windows Admins


August 9, 2017  4:59 AM

Get-ADUser filtering

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Active Directory, Powershell

Saw a question on the forums that revolved around Get-ADUser filtering.

Initial code was like this

Import-Csv .\users.txt |
foreach {
Get-ADUser -Filter {Name -like $_.Name}
}

which on the face of it seems reasonable. However, you get errors like this

Get-ADUser : Property: ‘Name’ not found in object of type:
‘System.Management.Automation.PSCustomObject’.
At line:3 char:3
+ Get-ADUser -Filter {Name -like $_.Name}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-ADUser], ArgumentException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.ArgumentException,Microsoft
.ActiveDirectory.Management.Commands.GetADUser

Change –like to –eq and you’ll still get the error.

This won’t work either:

Import-Csv .\users.txt |
foreach {
Get-ADUser -Filter {Name -like $($_.Name)}
}

You get messages about path errors.

Import-Csv .\users.txt |
foreach {
Get-ADUser -Filter {Name -like “$($_.Name)”}
}

will run but won’t return any data.

This will run and return the correct data.

Import-Csv .\users.txt |
foreach {
$name = $_.Name
Get-ADUser -Filter {Name -like $name}
}

Alternatively, you can use quotes so that the filter is a string

Import-Csv .\users.txt |
foreach {
Get-ADUser -Filter “Name -like ‘$($_.Name)'”
}

Another option is to use the LDAP filter syntax

Import-Csv .\users.txt |
foreach {
$name = $_.Name
Get-ADUser -LDAPFilter “(Name=$name)”
}

Import-Csv .\users.txt |
foreach {
Get-ADUser -LDAPFilter “(Name=$($_.Name))”
}

The help file about_activedirectory_filter is well worth reading. It doesn’t seem to be installed with the AD module on Windows Server 2016. You can read it on line at https://technet.microsoft.com/en-us/library/hh531527(v=ws.10).aspx

You’ll also see links to

about_ActiveDirectory – overview of module

about_ActiveDirectory_Identity

about_ActiveDirectory_ObjectModel

Get-ADUser filtering isn’t the most obvious of topics to get your head round but these examples should help you make your filters work. If you’re building a complex filter build it up a step at a time so you can test each stage.

August 1, 2017  4:00 AM

PowerShell Summit 2018: Call for topics

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

The call for topics for the PowerShell and DevOps Summit 2018 is now open – https://powershell.org/2017/08/01/76318/

We’re looking for sessions (45 or 105 minute) that span the whole range of PowerShell usage and knowledge PLUS sessions on DevOps practices.

This is your opportunity to speak at the premier PowerShell event of 2018.


July 31, 2017  2:20 PM

PowerShell documentation

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

The home of Microsoft’s PowerShell documentation is changing from MSDN to https://docs.microsoft.com

The PowerShell documentation is currently opened sourced at https://github.com/powershell/powershell-docs

This change makes accessing the documentation easier


July 27, 2017  6:08 AM

You have to laugh

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
64 bit windows 10

Sometimes things just happen and you have to laugh.

So I decided I wanted to get back to working with the Windows 10 Insider previews (and Windows Server previews). This time I decided to use VMs rather than my working machine so that interruptions were minimised.

I created a new Windows 10 VM and as normal for VMs I set the initial memory to 512MB and used dynamic memory so that the machine could claim more RAM if required. Windows 10 installed with no problems. (Remember this).

I then went into Window Update and signed into the Windows Insider program. After triggering a scan fro updates build 16241 showed up and started downloading. Great.

It tried to install but failed with a message that 2GB of RAM was needed to perform the install!

So I can install from scratch with less than 2GB of RAM but I can’t update the build unless I have 2GB RAM.

Nice piece of joined up thinking there guys.

Sometimes you just have to laugh.


July 20, 2017  4:11 AM

Unblock and rename files

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

I have a bunch of files that I’ve downloaded to a specific folder. I need to unblock and rename files in that folder. The rename involves replacing a plus sign with a space.

$path = 'C:\Users\Richard\Downloads\Walks'

$files = Get-ChildItem -Path $path -File

foreach ($file in $files) { 
 Unblock-File -Path $file.FullName
 $newname = $file.FullName -replace '\+', ' '
 Rename-Item -Path $file.FullName -NewName $newname
 }

Get-ChildItem -Path $path

I initially tried using a single pipeline but Unblock-File doesn’t generate any output which also blocks the pipeline – oops.

Read the list of files into an array. Iterate over the array and Unblock each file. Then rename the file. To use the –replace operator you need to escape the plus sign.

Display the files post rename as a check.


July 16, 2017  3:56 AM

Change a computer’s description

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
CIM, Powershell

The Win32_OperatingSystem class exposes the machines Description. This is how you can easily change a computer’s description.

PS> Get-CimInstance -ClassName Win32_OperatingSystem | select Description

Description
-----------

PS> Get-CimInstance -ClassName Win32_OperatingSystem | Set-CimInstance -Property @{Description = 'Richards Laptop'}
PS> Get-CimInstance -ClassName Win32_OperatingSystem | select Description

Description
-----------
Richards Laptop

You can see that the description is originally blank. Get the CimInstance of Win32_OperatingSystem and pipe it to Set-CimInstance. The property to change and its new value are held in the hash table that’s the value given to the –Property parameter. You can modify multiple properties at once – just add them as property-value pairs to the hash table


July 15, 2017  4:46 AM

Control split output

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

In this post I’ll show you show to control split output – that is control the number of strings that are returned.

If you use –split with just a delimiter you’ll get a split occurring at every occurrence of the delimiter:

PS> 'SundayJanuary 01 Jan 1 New Years Day First Monday if 1st is Saturday or Sunday' -split ' '
SundayJanuary
 01
 Jan
 1
 New
Years
 Day
 First
 Monday
 if
 1st
 is
 Saturday
 or
 Sunday

But we want the holiday information to be in a single string. Rather than spending effort putting it back together you can control the number of strings that are output:

PS> 'SundayJanuary 01 Jan 1 New Years Day First Monday if 1st is Saturday or Sunday' -split ' ',5
SundayJanuary
 01
 Jan
 1
New Years Day First Monday if 1st is Saturday or Sunday

In this case we’ve said we want 5 strings returned so everything after the 4th split is returned as a single string.

This makes our coding easier and neater

$uri = "http://www.officeholidays.com/countries/united_kingdom/index.php"
 $html = Invoke-WebRequest -Uri $uri
 $holidays = ($html.ParsedHtml.getElementsByTagName("table") | 
 where ClassName -eq 'list-table' | 
 select -ExpandProperty InnerText) -split "`n"

$holidays.Count

$hols = foreach ($holiday in $holidays[1..($holidays.Count -1)]){
 $x = $holiday -split ' ',5
 $y = $x[0] -split "day"
 
 $props = [ordered]@{
 DayOfWeek = "$($y[0])day"
 Day = $x[1]
 Month = $y[1]
 Holiday = $x[4]
 }
 
 New-Object -TypeName PSObject -Property $props
 
 }

$hols | Format-Table -AutoSize -Wrap

When I wrote this:

Office holidays

I said that the string handling was ugly and there must be a better way – I remembered!


July 12, 2017  10:13 AM

More diskinfo

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
CIM, Disk storage, Powershell

Yesterday I showed how to get the disk, partition and logical disk information using CIM. Today I want to show more diskinfo techniques.

This time we’ll use the Storage module which was introduced with Windows 8. Underneath the covers it uses CIM – just different classes. The storage module doesn’t differentiate between volumes and logical disks – it just uses volumes.

To start at the physical disk and get the partition and volumes:

$diskinfo = Get-Disk | foreach {

$parts = Get-Partition -DiskNumber $psitem.DiskNumber | where DriveLetter

$disk = $psitem

foreach ($part in $parts) {
 
 Get-Volume -Partition $part |
 foreach {
 $props = $null

$props = [ordered]@{
 Disk = $disk.Number
 Model = $disk.Model
 Firmware = $disk.FirmwareVersion
 SerialNUmber = $disk.SerialNumber
 'DiskSize(GB)' = [math]::Round(($disk.AllocatedSize / 1GB ), 2)
 Partitions = $disk.NumberOfPartitions
 Partition = $part.PartitionNumber
 BootPartition = $part.IsBoot
 'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2)
 VolumeBlockSize = $psitem.AllocationUnitSize
 LDiskName = $psitem.DriveLetter
 FileSystem = $psitem.FileSystem
 LDiskSize = [math]::Round(($psitem.Size / 1GB ), 2)
 LDiskFree = [math]::Round(($psitem.SizeRemaining / 1GB ), 2)
 }

New-Object -TypeName PSObject -Property $props
 }
 }
 }
 $diskinfo

And to go the other way

$diskinfo = Get-Volume | 
 where {$_.DriveLetter -AND $_.DriveType -eq 'Fixed'} | 
foreach {

$part = Get-Partition -DriveLetter $psitem.DriveLetter 
 
 $disk = Get-Disk -Partition $part

$props = $null

$props = [ordered]@{
 Disk = $disk.Number
 Model = $disk.Model
 Firmware = $disk.FirmwareVersion
 SerialNUmber = $disk.SerialNumber
 'DiskSize(GB)' = [math]::Round(($disk.AllocatedSize / 1GB ), 2)
 Partitions = $disk.NumberOfPartitions
 Partition = $part.PartitionNumber
 BootPartition = $part.IsBoot
 'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2)
 VolumeBlockSize = $psitem.AllocationUnitSize
 LDiskName = $psitem.DriveLetter
 FileSystem = $psitem.FileSystem
 LDiskSize = [math]::Round(($psitem.Size / 1GB ), 2)
 LDiskFree = [math]::Round(($psitem.SizeRemaining / 1GB ), 2)
 }

New-Object -TypeName PSObject -Property $props
 }
 $diskinfo

The number of blocks doesn’t seem to be available – suppose you could calculate it – otherwise the information is the same as with the CIM classes you saw last time. Some of the property names are different.


July 11, 2017  12:35 PM

Linking disks, partitions and logical drives

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
CIM, Disk storage, Powershell, WMI

A question of the forums was asking about discovering disk information. They were trying to pipe the output of Get-WmiObject into another Get-WmiObject. that won’t work. There is another way. On Windows machines physical drives are divided into 1 or more partitions which are each divided into one or more logical disks. Linking disks, partitions and logical drives is a relatively simple process.

You can start at the physical disk and work down to the logical disks or start at the logical disk and work back to the physical disk. Lets start with the logical disk.

$diskinfo = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3" |
foreach {
 $props = $null
 
 $part = Get-CimAssociatedInstance -InputObject $psitem -ResultClass Win32_DiskPartition
 $disk = Get-CimAssociatedInstance -InputObject $part -ResultClassName Win32_DiskDrive
 
 $props = [ordered]@{
 Disk = $disk.Index
 Model = $disk.Model
 Firmware = $disk.FirmwareRevision
 SerialNUmber = $disk.SerialNumber
 'DiskSize(GB)' = [math]::Round(($disk.Size / 1GB ), 2)
 Partitions = $disk.Partitions
 Partition = $part.index
 BootPartition = $part.BootPartition
 'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2)
 Blocks = $part.NumberOfBlocks
 BlockSize = $part.BlockSize
 LDiskName = $psitem.Caption
 FileSystem = $psitem.FileSystem
 LDiskSize = [math]::Round(($psitem.Size / 1GB ), 2)
 LDiskFree = [math]::Round(($psitem.FreeSpace / 1GB ), 2)
 }

New-Object -TypeName PSObject -Property $props

}

$diskinfo

Use Get-CimInstance to retrieve the instances of the Win32_LogicalDisk class. Use a filter for DriveType = 3 – which is local disks (as far as the server is concerned – they could be on a SAN or NAS).

Foreach of the disks get the associated partition and use that object to get the associated physical drive.

CIM (WMI) has the concept of associators and references.

A reference is a pointer showing you which instance is associated with another instance. For example:

PS> Get-CimInstance -ClassName Win32_LogicalDiskToPartition


 Antecedent : Win32_DiskPartition (DeviceID = "Disk #0, Partition #1")
 Dependent : Win32_LogicalDisk (DeviceID = "C:")
EndingAddress : 511578663935
StartingAddress : 368050176
PSComputerName :

Logical disk C: is associated with partition #1 on disk #0

If you want to actually get the associated class then you do this

PS> $ld = Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DeviceID = "C:"'
 PS> Get-CimAssociatedInstance -InputObject $ld -ResultClass Win32_DiskPartition

Name NumberOfBlocks BootPartition PrimaryPartition Size Index
 ---- -------------- ------------- ---------------- ---- -----
 Disk #0, Part... 998458230 False True 511210613760 1

or

PS> Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DeviceID = "C:"' | Get-CimAssociatedInstance -ResultClass Win32_DiskPartition

Name NumberOfBlocks BootPartition PrimaryPartition Size Index
 ---- -------------- ------------- ---------------- ---- -----
 Disk #0, Part... 998458230 False True 511210613760 1

Once you’ve go the partition and physical disk instances. Populate your output object and loop. Notice that the pipeline is output directly to the variable $diskinfo. You don’t need to build arrays – get the pipeline to do it for you.

Each logical disk gets an output like this

Disk : 0
 Model : Samsung SSD 840 PRO Series
 Firmware : DXM06B0Q
SerialNUmber : S1AXNSAF329511V
DiskSize(GB) : 476.93
 Partitions : 3
 Partition : 1
BootPartition : False
PartitionSize(GB) : 476.1
 Blocks : 998458230
BlockSize : 512
LDiskName : C:
FileSystem : NTFS
LDiskSize : 476.1
LDiskFree : 212.33

That’s working up the stack. What about working down. That’s a similar process:

$diskinfo = Get-CimInstance -ClassName Win32_DiskDrive |
foreach {
 $disk = $psitem
 
 $parts = Get-CimAssociatedInstance -InputObject $psitem -ResultClass Win32_DiskPartition

foreach ($part in $parts) {
 
 Get-CimAssociatedInstance -InputObject $part -ResultClassName Win32_LogicalDisk |
 foreach {
 $props = $null

$props = [ordered]@{
 Disk = $disk.Index
 Model = $disk.Model
 Firmware = $disk.FirmwareRevision
 SerialNUmber = $disk.SerialNumber
 'DiskSize(GB)' = [math]::Round(($disk.Size / 1GB ), 2)
 Partitions = $disk.Partitions
 Partition = $part.index
 BootPartition = $part.BootPartition
 'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2)
 Blocks = $part.NumberOfBlocks
 BlockSize = $part.BlockSize
 LDiskName = $psitem.Caption
 FileSystem = $psitem.FileSystem
 LDiskSize = [math]::Round(($psitem.Size / 1GB ), 2)
 LDiskFree = [math]::Round(($psitem.FreeSpace / 1GB ), 2)
 }

New-Object -TypeName PSObject -Property $props
 }
 }
 }
 $diskinfo

Start with getting the instances of Win32_Diskdrive. Foreach instance get the associated partitions – Win32_DiskPartition.

Iterate through the partitions and get the associated logical disk. Create your object and output.

NOTE: neither of these techniques will show the partitions that don’t contain logical drives so you won’t see the boot partition and other “hidden partitions” on modern Windows machines. if you need those look at Win32_DiskPartition directly.


July 9, 2017  1:42 PM

Office holidays

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

Office holidays are a great thing. They usually occur on public holidays. There’s a web site – www.officeholidays.com – you can use to discover the public holidays in your country. 133 countries are available – http://www.officeholidays.com/countries/index.php.

You can also use PowerShell to extract the information

$uri = "http://www.officeholidays.com/countries/united_kingdom/index.php"
 $html = Invoke-WebRequest -Uri $uri
 $holidays = ($html.ParsedHtml.getElementsByTagName("table") | 
 where ClassName -eq 'list-table' | 
 select -ExpandProperty InnerText) -split "`n"

$holidays.Count

$hols = foreach ($holiday in $holidays[1..($holidays.Count -1)]){
 $x = $holiday -split " "
 $y = $x[0] -split "day"
 
 $props = [ordered]@{
 DayOfWeek = "$($y[0])day"
 Day = $x[1]
 Month = $y[1]
 Holiday = $x[4..($x.Count-1)] -join " "
 }
 
 New-Object -TypeName PSObject -Property $props
 
 }

$hols | Format-Table -AutoSize –Wrap

Create a string to hold the uri of the country you want. You may need to use the website to discover how they format your country name. Use Invoke-WebRequest to get the html containing the holidays. You then need to get the inner text of the html.

DayDateHolidayComments
SundayJanuary 01 Jan 1 New Years Day First Monday if 1st is Saturday or Sunday
MondayJanuary 02 Jan 2 Day after New Years Day Scotland Only
MondayJanuary 02 Jan 2 New Years Day (observed) Except Scotland
TuesdayJanuary 03 Jan 3 New Year's Day (in lieu) Scotland Only
TuesdayFebruary 28 Feb 28 Pancake Tuesday Shrove Tuesday. Not a Public Holiday
WednesdayMarch 01 Mar 1 St Davids Day Wales Only. Not a public holiday
FridayMarch 17 Mar 17 St Patricks Day Northern Ireland Only
SundayMarch 26 Mar 26 Mothering Sunday Not a National Holiday
FridayApril 14 Apr 14 Good Friday Friday before Easter Sunday
MondayApril 17 Apr 17 Easter Monday Except Scotland
SundayApril 23 Apr 23 St George's Day England Only. Not a public holiday
MondayMay 01 May 1 Early May Bank Holiday First Monday in May
MondayMay 29 May 29 Spring Bank Holiday Last Monday in May
SundayJune 18 Jun 18 Father's Day 3rd Sunday in June. Not a public holiday
WednesdayJuly 12 Jul 12 Battle of the Boyne Northern Ireland Only
MondayAugust 07 Aug 7 August Bank Holiday Scotland Only. First Monday in August
MondayAugust 28 Aug 28 August Bank Holiday Last Monday in August (except Scotland)
SundayNovember 05 Nov 5 Guy Fawkes Night England Only. Not a public holiday
SundayNovember 12 Nov 12 Remembrance Sunday Not a public holiday. Sunday closest to 11 November
ThursdayNovember 30 Nov 30 St Andrews Day Scotland Only. If November 30 falls on a weekend, the next Monday is a bank ho liday instead
MondayDecember 25 Dec 25 Christmas Day
TuesdayDecember 26 Dec 26 Boxing Day

Its a single string but does contain new line characters so you can split it into an array.

Iterate through the array – skip the first line – and extract the day of the week, the day, month and holiday details. Create an object for output. Display the objects to see your holidays.

DayOfWeek Day Month Holiday 
 --------- --- ----- ------- 
 Sunday 01 January New Years Day First Monday if 1st is Saturday or Sunday 
 Monday 02 January Day after New Years Day Scotland Only 
 Monday 02 January New Years Day (observed) Except Scotland 
 Tuesday 03 January New Year's Day (in lieu) Scotland Only 
 Tuesday 28 February Pancake Tuesday Shrove Tuesday. Not a Public Holiday 
 Wednesday 01 March St Davids Day Wales Only. Not a public holiday 
 Friday 17 March St Patricks Day Northern Ireland Only 
 Sunday 26 March Mothering Sunday Not a National Holiday 
 Friday 14 April Good Friday Friday before Easter Sunday 
 Monday 17 April Easter Monday Except Scotland 
 Sunday 23 April St George's Day England Only. Not a public holiday 
 Monday 01 May Early May Bank Holiday First Monday in May 
 Monday 29 May Spring Bank Holiday Last Monday in May 
 Sunday 18 June Father's Day 3rd Sunday in June. Not a public holiday 
 Wednesday 12 July Battle of the Boyne Northern Ireland Only 
 Monday 07 August August Bank Holiday Scotland Only. First Monday in August 
 Monday 28 August August Bank Holiday Last Monday in August (except Scotland) 
 Sunday 05 November Guy Fawkes Night England Only. Not a public holiday 
 Sunday 12 November Remembrance Sunday Not a public holiday. Sunday closest to 11 November 
 Thursday 30 November St Andrews Day Scotland Only. If November 30 falls on a weekend, the next Monday is a bank holiday instead 
 Monday 25 December Christmas Day 
 Tuesday 26 December Boxing Day

I’ve used some rather ugly, brute force string handling. There should be a better way to extracting the data from the string but I need to think about it


Forgot Password

No problem! Submit your e-mail address below. We'll send you an e-mail containing your password.

Your password has been sent to: