PowerShell for Windows Admins


September 27, 2017  1:47 PM

Examples of replacing WMI cmdlet with CIM cmdlet

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

Following my last post I was asked about these Examples of replacing WMI cmdlet with CIM cmdlet.

Example 1

gwmi win32_operatingsystem -computername $Computer -credential $creds,

$cs = New-CimSession -Credential $creds -ComputerName $computer
Get-CimInstance -ClassName Win32_operatingsystem -CimSession $cs

Example 2

get-wmiobject -query “SELECT * FROM Meta_Class WHERE __Class = ‘Win32_Process’” -namespace “root\cimv2” -computername $computer -credential $creds

$cs = New-CimSession -Credential $creds -ComputerName $computer
Get-CimInstance -Query “SELECT * FROM Meta_Class WHERE __Class = ‘Win32_Process’” -CimSession $cs

Example 3

Get-WmiObject -Namespace $namespace -Class SMS_fullcollectionmembership -ComputerName $SCCMServer -filter “Name = ‘$computer’” -credential $creds

$cs = New-CimSession -Credential $creds -ComputerName $SCCMServer
Get-CimInstance -Namespace $namespace -ClassName SMS_fullcollectionmembership ” -filter “Name = ‘$computer’” -CimSession $cs

September 26, 2017  10:04 AM

CIM not WMI

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

I still see a lot of people using the WMI cmdlets – Get-WmiObject etc. You really should be using CIM nit WMI. In other words use Get-CimInstance rather than get-WmiObject etc etc.

Why do I say that?

Two main reasons.

Firstly, the WMI cmdlets are effectively deprecated. Any further development effort will be for the CIM cmdlets.

Secondly, and to my mind more important, is that the CIM cmdlets use WS-MAN for access to remote machines. If you have PowerShell remoting enabled you have access to the machine via a CIM session – either ephemeral using the cmdlet name or persistent using a CIM session.

The WMI cmdlets use DCOM for remoting which is blocked by default on the Windows firewall and most other firewalls and routers giving the RPC server is unavailable error.

The only time there is justification for using the WMI cmdlets is if you’re on a machine that has Powershell v2 installed and if that’s the case why haven’t you upgraded? If you can’t does that mean you’re running an application (usually Exchange or System Center) that doesn’t allow you to upgrade PowerShell.

Maybe its time to perform that upgrade.

As another thought PowerShell v6 includes the CIM cmdlets but not the WMI cmdlets!


September 25, 2017  1:08 PM

Write text to a file with Powershell

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

PowerShell is all about working with objects but sooner or later you’ll need to write text to a file with Powershell.

You have two options. The *-Content cmdlets and Out-File

PS> Get-Command *-Content

CommandType Name
 ----------- ----
 Cmdlet Add-Content
 Cmdlet Clear-Content
 Cmdlet Get-Content
 Cmdlet Set-Content


 PS> Get-Command *-File

CommandType Name
 ----------- ----
 Cmdlet Out-File
 Cmdlet Unblock-File

Lets start with Out-File

Out-File simply sends output to a file. Its the equivalent of the redirection operator but with parameters.

PS> Get-Process -Name power* | Out-File -FilePath gp1.txt
 PS> Get-Content gp1.txt

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
 ------- ------ ----- ----- ------ -- -- -----------
 672 33 124272 144548 1.88 6112 1 powershell
 767 31 86756 105336 2.64 11984 1 powershell

Whatever comes down the pipeline is redirected to the specified file instead of being displayed on screen.

By default Out-File (like redirection) will overwrite an existing file. You can append data to the file

PS> Get-Process -Name power* | Out-File -FilePath gp1.txt -Append
 PS> Get-Content gp1.txt

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
 ------- ------ ----- ----- ------ -- -- -----------
 672 33 124272 144548 1.88 6112 1 powershell
 767 31 86756 105336 2.64 11984 1 powershell

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
 ------- ------ ----- ----- ------ -- -- -----------
 673 33 124272 144580 1.88 6112 1 powershell
 798 32 73980 92564 2.64 11984 1 powershell

To protect an existing file that you don’t want overwriting IF it exists use the –NoClobber parameter

PS> Get-Process -Name power* | Out-File -FilePath gp1.txt -NoClobber
 Out-File : The file 'C:\test\gp1.txt' already exists.
 At line:1 char:28
 + Get-Process -Name power* | Out-File -FilePath gp1.txt -NoClobber
 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 + CategoryInfo : ResourceExists: (C:\test\gp1.txt:String) [Out-File], IOException
 + FullyQualifiedErrorId : NoClobber,Microsoft.PowerShell.Commands.OutFileCommand

You can overwrite a read only file with the –Force parameter.

By default Out-File uses unicode encoding. Other encodings are available

Unknown
String
Unicode
BigEndianUnicode
UTF8
UTF7
UTF32
ASCII
Default
OEM

This will be important if the file will be read by something other than PowerShell.

The *-Content cmdlets are different in that they expect strings

PS> Get-Process -Name power* | Set-Content -Path gp2.txt
 PS> Get-Content -Path gp2.txt
 System.Diagnostics.Process (powershell)
 System.Diagnostics.Process (powershell)

Set-Content and Add-Content use ASCII encoding by default. The other encodings are available

ASCII Uses the encoding for the ASCII (7-bit) character set.
BigEndianUnicode Encodes in UTF-16 format using the big-endian byte order.
BigEndianUTF32 Encodes in UTF-32 format using the big-endian byte order.
Default Encodes using the default value: ASCII.
Byte Encodes a set of characters into a sequence of bytes.
String Uses the encoding type for a string.
Unicode Encodes in UTF-16 format using the little-endian byte order.
UTF7 Encodes in UTF-7 format.
UTF8 Encodes in UTF-8 format.
Unknown The encoding type is unknown or invalid; the data can be treated as binary.

We can modify our previous example

PS> Get-Process -Name power* | Out-String | Set-Content -Path gp2.txt
 PS> Get-Content -Path gp2.txt

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
 ------- ------ ----- ----- ------ -- -- -----------
 627 34 156396 177492 3.00 6112 1 powershell
 726 32 71640 91540 3.17 11984 1 powershell

Set-Content will overwrite the content of a file or create a new file. Add-Content appends data to a file

PS> Get-Process -Name power* | Out-String | Add-Content -Path gp2.txt
 PS> Get-Content -Path gp2.txt

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
 ------- ------ ----- ----- ------ -- -- -----------
 627 34 156396 177492 3.00 6112 1 powershell
 726 32 71640 91540 3.17 11984 1 powershell


 Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
 ------- ------ ----- ----- ------ -- -- -----------
 676 34 156436 177648 3.05 6112 1 powershell
 744 32 71604 91540 3.27 11984 1 powershell

The help files for these cmdlets show so other examples.

NOTE: In PowerShell v6 the encodings will be standardised.


September 14, 2017  1:46 PM

Heterogeneous environments

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Windows Server

When we talk about heterogeneous environments the assumption is that we mean a mixture of Windows and Linux machines. Windows and Linux can be viewed as providing the end points of a spectrum of management issues. In reality there is another spectrum – that spectrum exists between Windows machines.

Thinking ONLY of server administration you could easily have Windows 2008, Windows 2008 R2, Windows 2012, Windows 2012 R2 and Windows 2016 servers in your environment. And that’s only the supported operating systems. From experience I’d expect that many organisations still have Windows 2003 servers and I suspect there are still some Windows 2000 servers in quite a few organisations. I wouldn’t be shocked to find a few Windows NT machines still in use!

The five supported Windows Server OSs are bad enough. Windows 2016 is moving to twice yearly updates starting this autumn (that’s fall for non-English readers). Windows 2008 & 2008 R2 go out of mainstream support in 2020 so giving the option for 5 versions of 2016 to be released before it vanishes. The Server 2012 family is supported until October 2023!

The point is that you need to think how you’re going to support a potentially large number of operating system editions going forward. I seriously doubt that anyone will be updating their whole server estate on a twice yearly basis.

Multiple small releases enable updates to particular areas of Windows being released quickly. So one release may update Hyper-V while another updates containers or storage.

Working out which Windows builds – and you will have to start thinking at the build level – will support particular versions of Exchange, SQL Server, SharePoint or any of the other Microsoft products. Expect other vendors of software that runs on top of windows server to panic when you ask them about supporting particular builds – they’re usually so slow at supporting new versions that the next is in beta. That behaviour is going to cause issues.

You could avoid some of this by saying you’ll skip builds – 1, 2, 3 or X years worth – but what happens when a new feature fixes a problem you’ve got now!

Heterogeneity is going to become more widespread. On the plus side it may kill the option of whole sale upgrades and massive server migration projects. You’ll just be permanently upgrading!

At the moment its not too big an issue but I expect over time that you’ll need to think about build number

PS> Get-CimInstance -ClassName win32_operatingsystem | select -ExpandProperty BuildNumber
15063

as much as version

PS> Get-CimInstance -ClassName win32_operatingsystem | select -ExpandProperty Version
10.0.15063

And that’s before we get to Linux.

If you’re writing scripts that do different things based on Windows version think about dropping down to build number as well. You can always use a –gt <build number> approach.

You’re going to be living in interesting times.


August 31, 2017  1:54 PM

PowerShell string concatenation

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

Strings – a list of characters such as ‘abcd’ – are a common feature of programming or scripting. Sometimes you need to join – concatenate – two or more strings together. This is how PowerShell string concatenation works.

First you need to know that strings can be defined with single quotes or double quotes:

PS> $sq = ‘A single quoted string’
PS> $sq
A single quoted string

PS> $dq = “A double quoted string”
PS> $dq
A double quoted string

The difference is that you can substitute into a double quoted string. Best practice is to use single quotes UNLESS you intend to substitute into the string

PS> ‘I am $sq’
I am $sq
PS> “I am $sq”
I am A single quoted string

String concatenation can be performed using the concatenation operator – a plus sign or through string substitution

PS> $s1 = ‘abcd’ + ‘defg’
PS> $s1
abcddefg

You can use variables

PS> $s1 = ‘abcd’
PS> $s2 = ‘defg’
PS> $s3 = $s1 + $s2
PS> $s3
abcddefg

When you concatenate strings like this you’re actually creating a new string not extending an existing one.

Alternatively, use string substitution

PS> $s4 = “$s1$s2”
PS> $s4
abcddefg


August 31, 2017  8:43 AM

PowerShell operators

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

PowerShell has operators – lots of operators. So many operators that it took us TWO chapters to work through them all in PowerShell in Action, third edition – https://www.manning.com/books/windows-powershell-in-action-third-edition. Here’s how you can discover the PowerShell operators.

PS> Get-Help about*operator*

Name
—-
about_Arithmetic_Operators
about_Assignment_Operators
about_Comparison_Operators
about_Logical_Operators
about_Operators
about_Operator_Precedence
about_Type_Operators

A quick listing gives us:

Arithmetic operators (+, -, *, /, %)

Assignment operators (=, +=, -=, *=, /=, %=)

Comparison operators (-eq, -ne, -gt, -lt, -le, -ge) (-match, -notmatch) (-like, -notlike)

(-in, -notin, -contains, -notcontains) (-bAND, -bOR, -bXOR, -bNOT)

Logical opertors (-and, -or, -xor, -not, !)

Redirection operators (>, >>, 2>, 2>, and 2>&1)

Type operators (-is, -isnot, -as)

And a bunch of special operators @(), & (call), [] (cast), comma, dot, –f , index opertor, pipeline operator, range operator, :: static member operator, $()

Also don’t forget –split, –join, –replace


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 – http://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


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: