PowerShell for Windows Admins

October 20, 2012  1:08 PM

UK PowerShell Group–November 2012

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

When: Tuesday, Nov 13, 2012 7:30 PM (BST)



PowerShell workflows are one of the major new features of PowerShell v3. This session will provide an introduction to using this functionality and some ideas of where they fit into administering your systems


Richard Siddaway has invited you to attend an online meeting using Live Meeting.
Join the meeting.
Audio Information
Computer Audio
To use computer audio, you need speakers and microphone, or a headset.
First Time Users:
To save time before the meeting, check your system to make sure it is ready to use Microsoft Office Live Meeting.
Unable to join the meeting? Follow these steps:

  1. Copy this address and paste it into your web browser:
  2. Copy and paste the required information:
    Meeting ID: SP6D69
    Entry Code: P\&c7-C=j
    Location: https://www.livemeeting.com/cc/usergroups

If you still cannot enter the meeting, contact support

Microsoft Office Live Meeting can be used to record meetings. By participating in this meeting, you agree that your communications may be monitored or recorded at any time during the meeting.

October 18, 2012  1:11 PM

A question of scope

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

I had a question left on my blog regarding scope.  The following is a simplified version of the problem

function level1 {            
 Write-Host '$iterations in level 1 = ' $iterations             
function level2 {            
 Write-Host '$iterations in level 2 =' $iterations            
$iterations = 0            
Write-Host '$iterations at start =' $iterations            
Write-Host '$iterations at end =' $iterations

Start with a variable $iterations. Call a function and increment the variable. Call a nested function and increment the variable and then display the result

The output is

$iterations at start = 0

$iterations in level 1 =  1

$iterations in level 2 = 2

$iterations at end = 0

Start with zero, increment in level1 and level 2 – all OK. Show the final result & its zero.

The reason is scope.  Each of the functions has its own scope. Those scopes are destroyed – along with any variables & their contents when the functions finish.

If you want to keep the results then change to this

function level1 {            
 Write-Host '$global:iterations in level 1 = ' $global:iterations             
function level2 {            
 Write-Host '$global:iterations in level 2 =' $global:iterations            
$global:iterations = 0            
Write-Host '$global:iterations at start =' $global:iterations            
Write-Host '$global:iterations at end =' $global:iterations

The output becomes

$global:iterations at start = 0

$global:iterations in level 1 =  1

$global:iterations in level 2 = 2

$global:iterations at end = 2

It is recommended not to use scopes in scripts if you can possibly avoid it – it can become very confusing in cases that are more complicated than this. The issue is better handled by passing parameters

function level1 {            
 param ($l1iterations)            
 Write-Host '$iterations in level 1 = ' $l1iterations            
 $l1iterations = level2 $l1iterations            
function level2 {            
param ($l2iterations)            
 Write-Host '$iterations in level 2 =' $l2iterations            
$iterations = 0            
Write-Host '$iterations at start =' $iterations            
$iterations = level1 $iterations            
Write-Host '$iterations at end =' $iterations

Much more on scope can be found in chapter 22 of my latest book PowerShell in Depth coauthored with Don Jones and Jeff Hicks -  http://www.manning.com/jones2/

October 16, 2012  2:27 PM

DnsClient module: #1 Get-DnsClientServerAddress

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Started to investigate the DnsClient module.  First cmdlet to catch my eye was Get-DnsClientServerAddress.

Always good to know a way to find the DNS server.

PS> Get-DnsClientServerAddress

InterfaceAlias               Interface Address ServerAddresses
Index     Family
————–               ——— ——- —————
Bluetooth Network Connection        19 IPv4    {}
Bluetooth Network Connection        19 IPv6    {fec0:0:0:ffff::1, fec0:0:0:ffff::2, fec0:0:0:ffff::3}
WiFi                                12 IPv4    {}
WiFi                                12 IPv6    {}
isatap.tiscali.co.uk                14 IPv4    {}
isatap.tiscali.co.uk                14 IPv6    {}
Ethernet                            13 IPv4    {}
Ethernet                            13 IPv6    {fec0:0:0:ffff::1, fec0:0:0:ffff::2, fec0:0:0:ffff::3}
Loopback Pseudo-Interface 1          1 IPv4    {}
Loopback Pseudo-Interface 1          1 IPv6    {fec0:0:0:ffff::1, fec0:0:0:ffff::2, fec0:0:0:ffff::3}
Teredo Tunneling Pseudo-I…        15 IPv4    {}
Teredo Tunneling Pseudo-I…        15 IPv6    {}

Now thats OK but I’d like a bit more information – especially the adapter and IP version.  We can get that data using Get-NetAdapter from the NetAdapter module.

Get-DnsClientServerAddress |             
where {$_.ServerAddresses -and $_.InterfaceAlias -notlike "Loop*" }|            
foreach {            
 $nic = $_            
 Get-NetAdapter -IncludeHidden -InterfaceIndex $($nic.InterfaceIndex) |            
 Add-Member -MemberType NoteProperty -Name ServerAddresses -Value $($nic.ServerAddresses) -PassThru |            
 Add-Member -MemberType NoteProperty -Name AddressFamily -Value $(if ($nic.AddressFamily -eq 2){"IPv4"}else{"IPv6"} ) -PassThru|            
 select Name, InterfaceDescription, ifIndex, Status, MacAddress, LinkSpeed, AddressFamily, ServerAddresses             

I restricted the output to those interfaces that had DNS server addresses. Used the interface to get the adapter – notice the use of –IncludeHidden – and then used Add-Member to add the addresses and Address family to the data.

These may be CDXML cmdlets but they work the same as any other cmdlet

October 15, 2012  11:21 AM

PowerShell 3 and Word

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

This is a common scenario

$word = New-Object -ComObject "Word.application"            
$word.visible = $true            
$doc = $word.Documents.Add()            
$word.Selection.Font.Name = "Cambria"            
$word.Selection.Font.Size = "20"            
$word.Selection.Font.Name = "Calibri"            
$word.Selection.Font.Size = "12"            
$word.Selection.TypeText("The best scripting language in the world!")            
$file = "c:\scripts\office\test1.doc"            

Create a new Word document – put some text into it and save it with a given file name.  I’ve used it successfully to create server documentation.

Unfortunately with PowerShell v3 it fails with this message

Exception calling “SaveAs” with “1” argument(s): “This is not a valid file name.

Try one or more of the following:

* Check the path to make sure it was typed correctly.

* Select a file from the list of files and folders.”

At line:17 char:1

+ $doc.SaveAs([REF]$file)

+ ~~~~~~~~~~~~~~~~~~~~~~~

+ CategoryInfo          : NotSpecified: (:) [], MethodInvocationException

+ FullyQualifiedErrorId : COMException

It appears not to like the [ref] but if you leave it out you get this

Argument: ‘1’ should be a System.Management.Automation.PSReference. Use [ref].

At line:18 char:1

+ $doc.SaveAs($file)

+ ~~~~~~~~~~~~~~~~~~

+ CategoryInfo          : NotSpecified: (:) [], MethodException

+ FullyQualifiedErrorId : NonRefArgumentToRefParameterMsg

[ref] isn’t case sensitive.

The only way round it that I know of is to create a blank Word document to use as a template

Copy-Item -Path mydoc.doc  -Destination testdoc.doc -Force            
$file = "C:\MyData\SkyDrive\Data\Scripts\Office-Word\testdoc.doc"            
$word = New-Object -ComObject "Word.application"            
$word.visible = $true            
$doc = $word.Documents.Open($file)            
$word.Selection.Font.Name = "Cambria"            
$word.Selection.Font.Size = "20"            
$word.Selection.Font.Name = "Calibri"            
$word.Selection.Font.Size = "12"            
$word.Selection.TypeText("The best scripting language in the world!")            

Notice that you need to give the full path to the file. Use the Open method and add the text. You can then save, close and quit the application.

I’ve tested this using office 2010 & office 2013 on Windows 7 & 8

Unfortunately we are still left with the problem that we can’t save the Word document into different formats.

October 14, 2012  12:46 PM

PowerShell summit

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

April 22-14  2013 – Microsoft campus.  PowerShell team and MVPs


Tickets going fast

October 14, 2012  4:07 AM

CDXML modules and nouns

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

CDXML modules don’t expose the verb and the noun of the cmdlets they publish.  If you want to discover the set of nouns in a CDXML module you need a bit of brute force:

Get-Command -Module DhcpServer |
foreach {
  ($_.Name -split "-")[1]
}  | sort | group -NoElement | sort count –Descending

October 12, 2012  12:08 PM

Windows 8 update

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

The first Windows 8 update became available this week.  This fixes some issues that have come up during the OEM process.  Fast and frequent updates sound good to me.

In addition number of apps have been updated – one I really like is the addition of conversation groupings to the mail app.  That’s made it a good bit more useful for me.

A recycle bin for Skydrive also appears to be new – at least I haven’t noticed it before

September 27, 2012  1:07 PM

Jobs, WMI, CIM and more jobs

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Background jobs were one of the most undervalued aspects of PowerShell v2. With the introduction of PowerShell v3 we get ways to schedule those jobs. In this post though I want to look at the new CIM cmdlets and jobs.

Using the WMI cmdlets most PowerShell users have done something like this:

PS> Get-WmiObject -Class Win32_ComputerSystem

Domain              : WORKGROUP
Manufacturer        : Hewlett-Packard
Model               : HP G60 Notebook PC
Name                : RSLAPTOP01
PrimaryOwnerName    : richard_siddaway@hotmail.com
TotalPhysicalMemory : 2951135232

The WMI cmdlets have the ability to be run as a background job by using the –AsJob parameter:

PS> Get-WmiObject -Class Win32_ComputerSystem  -AsJob

Id     Name            PSJobTypeName   State         HasMoreData     Location
–     —-            ————-   —–         ———–     ——–
4      Job4            WmiJob          Running       True            localhost

PS> Receive-Job -Id 4 -Keep

Domain              : WORKGROUP
Manufacturer        : Hewlett-Packard
Model               : HP G60 Notebook PC
Name                : RSLAPTOP01
PrimaryOwnerName    : richard_siddaway@hotmail.com
TotalPhysicalMemory : 2951135232

Notice the job type – WMI job

A number of other cmdlets have an –AsJob parameter:

PS> Get-Help * -Parameter *ASJob*


Notice that the list doesn’t include the CIM cmdlets. If you want to run those as jobs you need to use the standard job cmdlets.

PS> Start-Job -ScriptBlock {Get-CimInstance -ClassName Win32_ComputerSystem}

PS> Get-Job

Id     Name            PSJobTypeName   State
–     —-            ————-   —–
4      Job4            WmiJob          Completed
6      Job6            BackgroundJob   Completed

In this case the job type is Background job not WMI job.

One of the big things with the CIM cmdlets is CIM sessions – these are analogous to remoting sessions and also work over WSMAN

PS> $sw = New-CimSession -ComputerName $env:COMPUTERNAME

PS> Start-Job -ScriptBlock {Get-CimInstance -ClassName Win32_ComputerSystem -CimSession $sw}

PS> Get-Job

Id     Name            PSJobTypeName
–     —-            ————-
4      Job4            WmiJob
6      Job6            BackgroundJob
8      Job8            BackgroundJob

PS> Receive-Job -Id 8 -Keep
Cannot bind argument to parameter ‘CimSession’ because it is null.
    + CategoryInfo          : InvalidData: (:) [Get-CimInstance], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.Management.Infrastructure.CimCm
    + PSComputerName        : localhost

Oops – what happened.

The problem is that back ground jobs run in their own PowerShell process. That’s why you get the prompt back so quick. That process doesn’t contain the CIM session. The way round it is to create the CIM session as part of the job.

PS> $sb ={
>> $sw2 = New-CimSession -ComputerName $env:COMPUTERNAME
>> $sw2
>> Get-CimInstance -ClassName Win32_ComputerSystem -CimSession $sw2
>> }
PS> Start-Job -ScriptBlock $sb

You then get a job that completes and gives you data you can use.

Just remember that if you want to use CIM sessions in a job then create them as part of the job. You should also put in a line to remove them Smile

September 24, 2012  12:10 PM


Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Every time I look at PowerShell v3 I seem to find a new way to access WMI!

I’ve covered the –ComputerName and –CimSession parameters before but to recap

We duplicate the way Get-WmiObject works:

$computer = $env:COMPUTERNAME
Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $computer

We can use CIM sessions
$cs = New-CimSession -ComputerName $computer
Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $cs

One little known and little used capability in PowerShell v2 was the WSMAN cmdlets. These enable us to access WMI directly over WSMAN

$ruri = “http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_OperatingSystem”
Get-WSManInstance -ResourceURI $ruri -ComputerName $computer

This is fine for a single returned instance but where there are multiples eg disks we need to do a bit more work

$ruri = http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_LogicalDisk

Get-WSManInstance -ResourceURI $ruri -ComputerName $computer -Enumerate |
select DeviceId, Description

It turns out that we can use these ResourceURIs with Get-CimInstance

$ruri = http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_OperatingSystem

Get-CimInstance -ResourceUri $ruri -ComputerName $computer
Get-CimInstance -ResourceUri $ruri -CimSession $cs

Better still if we are going after multiple instances

$ruri = “http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_LogicalDisk”
Get-CimInstance -ResourceUri $ruri -ComputerName $computer |
select DeviceId, Description

Get-CimInstance -ResourceUri $ruri -CimSession $cs |
select DeviceId, Description

You have to use –ComputerName OR –CimSession if you use –ResourceURI.  If you don’t you make a DCOM connection to the local machine and you request will fail. You also won’t be able to use –ResourceURI if your CIM session is over DCOM

September 23, 2012  9:40 AM

PowerShell remoting and upgrading to Windows 2012

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

I have a number of  virtual machines running Windows 2008 R2 and I upgraded one of them to Windows 2012 – an over the top upgrade rather than a format and clean install

I found that Get-PSSession –Computername didn’t work against that machine.

This parameter now looks at the remote machine endpoint and gets all remoting sessions connected to the machine. I verified that the command worked correctly against a clean install of Windows 2012. This pointed to a WSMAN configuration issue.

I found two issues:

  • PSversion remained on 2.0 after the upgrade
  • SDKversion remained on 1 after the upgrade

You can’t just do a blanket upgrade on SDKversion because some of the endpoints remain on 1 even in a clean Windows 2012 install.

This is what I ended up doing:

Set-Item -Path wsman:\localhost\plugin\microsoft.powershell\InitializationParameters\PSVersion -Value 3.0
Set-Item -Path wsman:\localhost\plugin\microsoft.ServerManager\InitializationParameters\PSVersion -Value 3.0
Set-Item -Path wsman:\localhost\plugin\Microsoft.PowerShell32\InitializationParameters\PSVersion -Value 3.0

Set-Item -Path wsman:\localhost\plugin\Microsoft.PowerShell32\SDKVersion -Value 2
Set-Item -Path wsman:\localhost\plugin\Microsoft.PowerShell\SDKVersion -Value 2
Restart-Service winrm

That didn’t completely fix it so I restarted the remote machine then ran:

Disable-PSRemoting –Force
Enable-PSRemoting –Force

That seemed to fix the problem.

At the moment all seems to be working BUT I’ve more testing to do before I’m 100% happy with this.  This isn’t a procedure I’d recommend unless you exactly what you are doing and why.

Don’t try this at home until you are sure you need to.

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: