PowerShell for Windows Admins


August 30, 2017  8:59 AM

Comparing AD group membership on EmployeeId

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

Back in this post – https://itknowledgeexchange.techtarget.com/powershell/comparing-group-membership/ I showed how to compare the membership of two groups using Compare-Object. The comparison was based on the samAccountName. A question raised the issue of comparing AD group membership on EmployeeId

In the case in particular users have multiple accounts BUT the EmployeeId is correct on all and will therefore show matching users. Assuming the EmployeeId is correct on all accounts it still leaves a problem.

When you run Get-ADGroupMember you get a very limited number of properties returned:

PS> Get-ADGroupMember -Identity Testgroup1

distinguishedName : CN=JONES James,OU=UserAccounts,DC=Manticore,DC=org
 name : JONES James
objectClass : user
objectGUID : 027cb406-a3b0-4f45-9bbd-db47ccfb9212
 SamAccountName : JamesJones
 SID : S-1-5-21-759617655-3516038109-1479587680-1225

First thing I needed to do was set up some users with an EmployeeId

$ei = 1
 Get-ADUser -Filter {Name -like "*Jones*"} -Properties EmployeeId |
foreach {
 $id = 23945 + $ei
 
 $psitem | Set-ADUser -EmployeeID $id

$ei = $ei + (Get-Random -Minimum 3 -Maximum 12)
 }

Get a set of users – including the EmployeeId – and forech of them set the id. The id is randomly generated based on a starting value and increment.

Now that the users have an Employeeid you can use that for comparison purposes

$group1 = Get-ADGroupMember -Identity Testgroup1 | 
foreach {
 Get-ADUser -Identity $psitem.distinguishedName -Properties EmployeeId | 
 select -ExpandProperty EmployeeId
 }

$group2 = Get-ADGroupMember -Identity Testgroup2 | 
foreach {
 Get-ADUser -Identity $psitem.distinguishedName -Properties EmployeeId | 
 select -ExpandProperty EmployeeId
 }


 Compare-Object -ReferenceObject $group1 -DifferenceObject $group2 -IncludeEqual | 
 where SideIndicator -eq "==" | 
foreach { 
 $id = ($_.InputObject) 
 
 Get-ADUser -Filter {EmployeeId -eq $id} -Properties EmployeeId 
 }

Get the membership of the first group and for each member use Get-ADUser to return the EmployeeId. Repeat for the second group.

Use Compare-Object to compare the two sets of group members – you’re looking for matches indicated by “==”

Foreach match get the AD user account filtering on the EmployeeID.

The PROBLEM with this approach is that you’ll get all user accounts returned that have the particular EmployeeId. You can replace the line

Get-ADUser -Filter {EmployeeId -eq $id} -Properties EmployeeId

with

Get-ADUser -Filter {EmployeeId -eq $id} -Properties EmployeeId, MemberOf | where {$_.MemberOf -like “*Testgroup1*” -AND $_.MemberOf -like “*Testgroup2*”}

Which should resolve the problem

August 28, 2017  1:26 PM

PowerShell substrings

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

PowerShell is all about working with objects but you often have to drop to a lower level and work with properties and their values. Many objects have properties that are strings – a string is one of the standard PowerShell literals – and sometimes you want to extract part of a string – a substring. This post shows how PowerShell substrings work.

First thing to note is that PowerShell doesn’t have a substring command or keyword. A string in PowerShell is an instance of the System.String class and so you use the Substring method of the String class.

You have 2 options – technically known as overloads. You can view a methods overloads by using the method name without brackets

PS> ‘abcdefghijklmnopqrstuvwxyz’.Substring

OverloadDefinitions
——————-
string Substring(int startIndex)
string Substring(int startIndex, int length)

In the first case you give a starting index into the string and the substring starts at that point and takes everything to the end of the string:

PS> 0..25 | foreach {‘abcdefghijklmnopqrstuvwxyz’.Substring($psitem)}
abcdefghijklmnopqrstuvwxyz
bcdefghijklmnopqrstuvwxyz
cdefghijklmnopqrstuvwxyz
defghijklmnopqrstuvwxyz
efghijklmnopqrstuvwxyz
fghijklmnopqrstuvwxyz
ghijklmnopqrstuvwxyz
hijklmnopqrstuvwxyz
ijklmnopqrstuvwxyz
jklmnopqrstuvwxyz
klmnopqrstuvwxyz
lmnopqrstuvwxyz
mnopqrstuvwxyz
nopqrstuvwxyz
opqrstuvwxyz
pqrstuvwxyz
qrstuvwxyz
rstuvwxyz
stuvwxyz
tuvwxyz
uvwxyz
vwxyz
wxyz
xyz
yz
z

Remember that in .NET indices start at 0 so the string is 26 characters long with indices 0-25

If you supply an index that would be beyond the end of the string:

PS> ‘abcdefghijklmnopqrstuvwxyz’.Substring(26)

PS> ‘abcdefghijklmnopqrstuvwxyz’.Substring(30)
Exception calling “Substring” with “1” argument(s): “startIndex cannot be larger than length of string.
Parameter name: startIndex”
At line:1 char:1
+ ‘abcdefghijklmnopqrstuvwxyz’.Substring(30)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ArgumentOutOfRangeException

In the first case you get nothing back because the string is 26 characters long. Anything larger than that and you get an error.

The second overload of substring involves supplying a starting index and the number of characters you want to take (including the starting character):

PS> 0..25 | foreach {‘abcdefghijklmnopqrstuvwxyz’.Substring($psitem, 3)}
abc
bcd
cde
def
efg
fgh
ghi
hij
ijk
jkl
klm
lmn
mno
nop
opq
pqr
qrs
rst
stu
tuv
uvw
vwx
wxy
xyz
Exception calling “Substring” with “2” argument(s): “Index and length must refer to a location within the string.
Parameter name: length”
At line:1 char:18
+ 0..25 | foreach {‘abcdefghijklmnopqrstuvwxyz’.Substring($psitem, 3)}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ArgumentOutOfRangeException

Exception calling “Substring” with “2” argument(s): “Index and length must refer to a location within the string.
Parameter name: length”
At line:1 char:18
+ 0..25 | foreach {‘abcdefghijklmnopqrstuvwxyz’.Substring($psitem, 3)}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ArgumentOutOfRangeException

Notice the error when you would go beyond the end of the string

PowerShell substrings depend on using the SubString method of the System.String class. The examples here will help you decide which overload to use.


August 25, 2017  4:25 AM

Further information on PowerShell 2.0 deprecation

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
PowerShell 2

The PowerShell team have provided further information about the deprecation of PowerShell 2.0

Windows PowerShell 2.0 Deprecation

One point that didn’t come out is that if you remove PowerShell 2.0 your CIM sessions can all run over WS-MAN. DCOM isn’t required any more – YAY


August 23, 2017  10:37 AM

PowerShell pause

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

PowerShell pause – how can you pause a PowerShell script?

Two ways come to mind.

First if you just want the script to pause for a specified time period then you can use Start-Sleep

1..10 |
foreach {
$PSItem
if ($PSItem -eq 5) {
Write-Warning -Message “Starting sleep”
Start-Sleep -Seconds 5
}
}

Run this and you’ll see the numbers 1-5 output then then warning message. After the delay you’ll see the numbers 6-10 output.

But what if you want to control the pause manually? Not sure if there are advantages to this approach but if you do need to do this you can use Read-Host

1..10 |
foreach {
$PSItem
if ($PSItem -eq 5) {
Read-Host -Prompt “Press Enter key to continue”
}
}

You’ll see the numbers 1-5 output then the message

Press Enter key to continue:

After pressing the enter key the script continues and outputs 6-10

Don’t know why you’d want to do this in an automation scenario but the technique is there if you need it – I don’t recommend the approach.

There are also a few cmdlets that can be used under specific circumstances:

Wait-Event
Wait-Job
Wait-Process
Wait-VM

Also check the –wait parameter on Restart-Computer


August 21, 2017  7:48 AM

PowerShell 2.0 to be deprecated in Windows 10

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
PowerShell 2

You’ve been able to install the PowerShell 2.0 engine side by side with the latest version of PowerShell for the last few versions of PowerShell. Not any more. PowerShell 2.0 to be deprecated in Windows 10 Fall Creators update later this year.

https://support.microsoft.com/en-us/help/4034825/features-that-are-removed-or-deprecated-in-windows-10-fall-creators-up

Presumably this will also apply to Windows Server 2016 which is also moving to a twice yearly update


August 20, 2017  2:10 PM

PowerShell for loop

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

Loops are a construction seen in most scripting and programming languages. A loop is used to repeat a set of statements a set number of times or until a specific criterion is met or while a specific criterion is true. In this post I’ll describe the PowerShell for loop.

For loops are found in many languages. A for loop is sometimes referred to as a counting loop as it will have a counter that starts at a pre-set value and counts up to a specific value. The counter is usually incremented by 1 for each iteration of the loop.

A PowerShell for loop looks like this

for ($i=1; $i -le 10; $i++){$i}

The counter – $i – is initialised to 1. The loop will execute while $i is less than or equal to 10 and $i is incremented by 1 for each turn round the loop. In this case the loop lists the value of the counter. You can see the results like this

PS> $results = for ($i=1; $i -le 10; $i++){$i}
PS> “$results”
1 2 3 4 5 6 7 8 9 10

You can also run loops where the counter decreases

PS> $results = for ($i=10; $i -ge 1; $i–){$i}
PS> “$results”
10 9 8 7 6 5 4 3 2 1

A for loop is great when you need to perform the loop and exact number of times but if your loop depends on a specific criterion you’re better off using a while loop or a do loop which I’ll cover in another post


August 19, 2017  7:52 AM

Create a directory

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

PowerShell enables you to work with the file system on your machine – one question that often comes up is how to create a directory.

When working interactively you can use md

PS> md c:\testf1


 Directory: C:\


 Mode LastWriteTime Length Name
 ---- ------------- ------ ----
 d----- 19/08/2017 14:24 testf1

md doesn’t look like a PowerShell command – more like an old style DOS command.

Its actually an alias for mkdir

PS> Get-Command md

CommandType Name Version Source
 ----------- ---- ------- ------
 Alias md –> mkdir

Which raises the question – what’s mkdir?

PS> Get-Command mkdir

CommandType Name Version Source
 ----------- ---- ------- ------
 Function mkdir

Its a function that PowerShell creates for you

Digging into the function

PS> Get-ChildItem -Path function:\mkdir | select -ExpandProperty Definition

<#
 .FORWARDHELPTARGETNAME New-Item
 .FORWARDHELPCATEGORY Cmdlet
 #>

[CmdletBinding(DefaultParameterSetName='pathSet',
 SupportsShouldProcess=$true,
 SupportsTransactions=$true,
 ConfirmImpact='Medium')]
 [OutputType([System.IO.DirectoryInfo])]
param(
 [Parameter(ParameterSetName='nameSet', Position=0, ValueFromPipelineByPropertyName=$true)]
 [Parameter(ParameterSetName='pathSet', Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)]
 [System.String[]]
 ${Path},

[Parameter(ParameterSetName='nameSet', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
 [AllowNull()]
 [AllowEmptyString()]
 [System.String]
 ${Name},

[Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
 [System.Object]
 ${Value},

[Switch]
 ${Force},

[Parameter(ValueFromPipelineByPropertyName=$true)]
 [System.Management.Automation.PSCredential]
 ${Credential}
 )

begin {

try {
 $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('New-Item', [System.Management.Automation.CommandTypes]
 ::Cmdlet)
 $scriptCmd = {& $wrappedCmd -Type Directory @PSBoundParameters }
 $steppablePipeline = $scriptCmd.GetSteppablePipeline()
 $steppablePipeline.Begin($PSCmdlet)
 } catch {
 throw
 }

}

shows that its based on New-Item

PS> New-Item -Path c:\ -Name testf2 -ItemType Directory


 Directory: C:\


 Mode LastWriteTime Length Name
 ---- ------------- ------ ----
 d----- 19/08/2017 14:32 testf2

The default for New-Item in the filesystem is to create a file so you need to use –ItemType Directory to create the folder.

If the folder you’re creating is a subfolder of a non-existent folder you can create the hierarchy is one go

PS> New-Item -Path c:\ -Name testf3\tests1 -ItemType Directory


 Directory: C:\testf3


 Mode LastWriteTime Length Name
 ---- ------------- ------ ----
 d----- 19/08/2017 14:33 tests1


 PS> Get-ChildItem -Path c:\testf3 -Recurse


 Directory: C:\testf3


 Mode LastWriteTime Length Name
 ---- ------------- ------ ----
 d----- 19/08/2017 14:33 tests1

This can get complicated if you try to nest too many levels so I recommend explicitly creating each level of your folder hierarchy. Its much easier to maintain and modify


August 18, 2017  3:00 AM

PowerShell foreach

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

PowerShell has a number of ways to perform a loop – I recently counted seven distinct methods. If you can’t list them all don’t worry one is very esoteric and unexpected. I’ll enumerate them in a future post. For now I want to concentrate on a source of confusion – especially to newcomers to PowerShell – namely the PowerShell foreach statements.

The confusion arises because there are effectively two foreach statements. One is a PowerShell keyword that initiates a loop and the other is an alias for a cmdlet.

Lets start with the foreach loop.

$numbers = 1..10
foreach ($number in $numbers){
[math]::Pow($number, 2)
}

foreach in this case is used to iterate over a collection. In the example above $numbers is an array of numbers 1 to 10. Foreach number in the array it is raised to the power 2 – squared.

Remember that PowerShell is unique among shells in that you can use pipelines in many places that other languages insist on variables so you could change the example to

foreach ($number in 1..10){
[math]::Pow($number, 2)
}

The array is generated and then iterated over as earlier.

If you see foreach as the first command on a line you’re dealing with the foreach keyword and therefore a loop.

On the other hand if you see foreach in a pipeline

1..10 | foreach {
[math]::Pow($_, 2)
}

or

1..10 | foreach {
[math]::Pow($psitem, 2)
}

you’re dealing with the cmdlet. $_ or $psitem denote the object currently on the pipeline. foreach is an alias for the Foreach-Object cmdlet and you’re using –Process as a position parameter for the scriptblock. Written in full you’re doing this

1..10 | ForEach-Object -process {
[math]::Pow($_, 2)
}

or
1..10 | ForEach-Object -process {
[math]::Pow($psitem, 2)
}
Just to add to the confusion you also have the option to use the foreach method on the collection

(1..10).foreach({[math]::Pow($psitem, 2)})

This isn’t seen as much though it should be remembered as this is the fastest way to iterate over a collection.

In summary

foreach starting a line is the looping keyword. Faster than the pipeline but increases memory overheads as the collection has to pre-generated

foreach on the pipeline is an alias for foreach-object. Lower memory requirements as the collection is passed down the pipeline but a bit slower

().foreach({}) is a method on the collection (we treat it as an operator in PowerShell in Action) and is fast but in terms of coding style may be more intuitive to developers than 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.


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: