PowerShell for Windows Admins

November 16, 2016  11:22 AM

Exploring PowerShell automation

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Books, Powershell

My PowerShell books have all been published by Manning, A while back they asked me to put together a selection of extracts that show the depth and breadth of PowerShell. Its now available – for free – https://www.manning.com/books/exploring-powershell-automation

The book highlights PowerShell remoting and administering SQL Server, IIS and Active Directory through PowerShell. These are core skills these days and the book will give you a good introduction to these areas

November 15, 2016  12:48 PM

PowerShell 10 year anniversary videos

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Yesterday was the PowerShell 10 year anniversary event – broadcast live on channel 9

The session recordings are available


November 11, 2016  11:29 AM

Hyper-V book

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
book, Hyper-V

I’ve been working with Andy Syrewicze on Learn Hyper-V in a Month of Lunches.

Its now available in Manning’s Early Access program (MEAP) https://www.manning.com/books/learn-hyper-v-in-a-month-of-lunches

Until 14 November 2016 you can get the MEAP for half price using code mlsyrewicze

November 5, 2016  10:59 AM

Creating a new AD forest

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Active Directory, Powershell, Windows Server 2016

As I’ve completely rebuilt my demo/lab machine I need to re-create the Active Directory

This is now so simple even on a server core machine

First install the roles and features needed

Add-WindowsFeature -Name AD-Domain-Services, RSAT-AD-PowerShell, DNS, RSAT-DNS-Server, DHCP, RSAT-DHCP

This adds AD, DNS, DHCP and the appropriate admin tools – as its server core we’re really talking about the relevant PowerShell modules

Installing AD just gets you ready – it doesn’t create the forest

You get the ADDSDeployment module

PS C:\Scripts> Get-Command -Module ADDSDeployment


To create the forest and the first domain controller

PS C:\Scripts> Install-ADDSForest -DomainName ‘Manticore.org’ -ForestMode Default -DomainMode Default -InstallDns
SafeModeAdministratorPassword: ********

You’ll be asked to confirm the safe mode password

Default for forest and domain mode matches the Windows version

PS C:\Users\Administrator> Get-ADForest
ApplicationPartitions : {}
CrossForestReferences : {}
DomainNamingMaster    : W16DC01.Manticore.org
Domains               : {Manticore.org}
ForestMode            : Windows2016Forest
GlobalCatalogs        : {W16DC01.Manticore.org}
Name                  : Manticore.org
PartitionsContainer   : CN=Partitions,CN=Configuration,DC=Manticore,DC=org
RootDomain            : Manticore.org
SchemaMaster          : W16DC01.Manticore.org
Sites                 : {Default-First-Site-Name}
SPNSuffixes           : {}
UPNSuffixes           : {}


PS C:\Users\Administrator> Get-ADDomain
AllowedDNSSuffixes                 : {}
ChildDomains                       : {}
ComputersContainer                 : CN=Computers,DC=Manticore,DC=org
DeletedObjectsContainer            : CN=Deleted Objects,DC=Manticore,DC=org
DistinguishedName                  : DC=Manticore,DC=org
DNSRoot                            : Manticore.org
DomainControllersContainer         : OU=Domain Controllers,DC=Manticore,DC=org
DomainMode                         : Windows2016Domain
DomainSID                          : S-1-5-21-759617655-3516038109-1479587680
ForeignSecurityPrincipalsContainer : CN=ForeignSecurityPrincipals,DC=Manticore,DC=org
Forest                             : Manticore.org
InfrastructureMaster               : W16DC01.Manticore.org
LastLogonReplicationInterval       :
LinkedGroupPolicyObjects           : {CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=Policies,CN=System,DC=Manticore,DC=o
LostAndFoundContainer              : CN=LostAndFound,DC=Manticore,DC=org
ManagedBy                          :
Name                               : Manticore
NetBIOSName                        : MANTICORE
ObjectClass                        : domainDNS
ObjectGUID                         : 05d9aa61-d422-4728-9595-77754934b948
ParentDomain                       :
PDCEmulator                        : W16DC01.Manticore.org
PublicKeyRequiredPasswordRolling   : True
QuotasContainer                    : CN=NTDS Quotas,DC=Manticore,DC=org
ReadOnlyReplicaDirectoryServers    : {}
ReplicaDirectoryServers            : {W16DC01.Manticore.org}
RIDMaster                          : W16DC01.Manticore.org
SubordinateReferences              : {CN=Configuration,DC=Manticore,DC=org}
SystemsContainer                   : CN=System,DC=Manticore,DC=org
UsersContainer                     : CN=Users,DC=Manticore,DC=org

November 4, 2016  10:51 AM

ComputerName parameters for CIM and WMI cmdlets

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

Accessing a remote system and running

Get-WmiObject -ClassName Win32_LogicalDisk -ComputerName $computer


Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $computer

is a standard approach.

If you’re creating a function with that code in you may put the local machine as a default parameter:

$computer = $env:COMPUTERNAME

Running Get-WmiObject locally will work quite happily because you’re using COM to access the local machine.

Get-CimInstance may well fail with this error

PS> Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $computer
Get-CimInstance : The client cannot connect to the destination specified in the request. Verify that the service on the destination is running and is accepting requests. Consult the logs anddocumentation for the WS-Management service running on the destination, most commonly IIS or WinRM.
If the destination is the WinRM service, run the following command on the destination to analyze and configure the WinRM service: “winrm quickconfig”.
At line:1 char:1
+ Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $computer
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : ConnectionError: (root\cimv2:Win32_LogicalDisk:String) [Get-CimInstanc
e], CimException
+ FullyQualifiedErrorId : HRESULT 0x80338012,Microsoft.Management.Infrastructure.CimCmdlets.GetC
+ PSComputerName        : RSSURFACEPRO2

The CIM cmdlets use WSMAN to connect to remote machines. This is triggered by using the –ComputerName parameter. The error means you haven’t got the winrm service running on the local machine. On modern Windows remoting, and therefore winrm, are enable by default for servers but disable for client OS e.g. Windows 10. Easiest way to get this to work is run Enable-PSremoting from and elevated prompt.

November 4, 2016  10:03 AM

Working with multiple CIM objects

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

Many of the CIM objects we work with in our computers come in multiple instances – disks and network cards are a couple of examples. Many times when you see examples you’ll see something like this:

$disks = Get-WmiObject -Class Win32_LogicalDisk

foreach ($disk in $disks){
if ($disk.Size -gt 0) {
$disk | select DeviceId,
@{N=’Free’; E={[math]::Round($disk.FreeSpace/1GB, 2)}},
@{N=’Size’; E={[math]::Round($disk.Size/1GB, 2)}},
@{N=’PercUsed’; E={[math]::Round((($disk.Size – $disk.FreeSpace) / $disk.Size) * 100, 2)}}

Get a collection of objects. Iterate through them with foreach and do something.

You can, and should, do this as a pipeline operation. The code above is really a hangover from VBScript coding.

Converting the above to a pipeline gives

Get-WmiObject -Class Win32_LogicalDisk |
where Size -gt 0 |
foreach {
$_ | select DeviceId,
@{N=’Free’; E={[math]::Round($_.FreeSpace/1GB, 2)}},
@{N=’Size’; E={[math]::Round($_.Size/1GB, 2)}},
@{N=’PercUsed’; E={[math]::Round((($_.Size – $_.FreeSpace) / $_.Size) * 100, 2)}}

NOTE – before anyone starts complaining yes I know you can use a  filter on Get-WmiObject I’m explaining the principle! Also, I know that you could go straight into the select on the pipeline but if you want to add extra processing e.g. send an email if the disk is more than 80% used you need the foreach

You can do similar things with NICs for example

Get-WmiObject -ClassName Win32_PerfFormattedData_Tcpip_NetworkInterface |
where CurrentBandwidth -gt 0 |
foreach {

$props = [ordered]@{
Name = $psitem.Name
Computer = $psitem.PSComputerName
PercUtilisation = (($psitem.BytesTotalPersec * 8) / $psitem.CurrentBandWidth) * 100

New-Object -TypeName PSObject -Property $props

} |
where PercUtilisation -gt 0.5 |
foreach {
$text =  @”
$($_.Name) on $($_.Computer)

Bandwidth utilized:  $($_.PercUtilisation) %



Rather than displaying $text send an email if the utilisation is too big.

where PercUtilisation -gt 0.5

is used so I actually get to see results. You probably want 60% or more on your production machines.

November 3, 2016  9:00 AM

New Hyper-V switch on Windows 10

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Hyper-V, Windows 10

My test/lab machine had been running Windows Server 2016 TP 5. With Server 2016 now RTM it was time for a rebuild.  Unfortunately, 2016 RTM carried on from TP5 and decided not to work with my wireless card.

Decided to try using Hyper-V on Windows 10 – it recognises the wifi card and is happy to work with it.

After installing the Hyper-V feature I needed to create some Hyper-v virtual switches

PS> New-VMSwitch -SwitchType External -Name ‘LAN’ -NetAdapterName ‘LAN’
New-VMSwitch : Cannot validate argument on parameter ‘SwitchType’. The argument “External” does not belong to the set
“Internal,Private” specified by the ValidateSet attribute. Supply an argument that is in the set and then try the
command again.
At line:1 char:26
+ New-VMSwitch -SwitchType External -Name ‘LAN’ -NetAdapterName ‘LAN’
+                          ~~~~~~~~
+ CategoryInfo          : InvalidData: (:) [New-VMSwitch], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.HyperV.PowerShell.Commands.NewVMSwitch

But External is a valid switch type

PS> New-VMSwitch -SwitchType x
New-VMSwitch : Cannot bind parameter ‘SwitchType’. Cannot convert value “x” to type
“Microsoft.HyperV.PowerShell.VMSwitchType”. Error: “Unable to match the identifier name x to a valid enumerator name.
Specify one of the following enumerator names and try again:
Private, Internal, External”
At line:1 char:26
+ New-VMSwitch -SwitchType x
+                          ~
+ CategoryInfo          : InvalidArgument: (:) [New-VMSwitch], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.HyperV.PowerShell.Commands.NewVMSwitch

Wonder how many of these issues I’m going to find!

October 31, 2016  10:52 AM

Don’t reinvent the wheel

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell, Windows Server

Way back when I used to take Microsoft certification exams there were often questions of the form “Perform task X with the minimum of administrative effort” Most, if nor all, of the possible answers would be correct but the correct answer was the one that achieved the goal with the minimum amount of work.

Many, if not most, administrators don’t seem to follow that model.

This was brought home to me when I saw a forum discussion about collecting event log information from a bunch of remote servers on a regular basis.

You could set up a scheduled task/job that runs a script against the remote servers – collects the  log information and populates an Excel spreadsheet


You could enable event log forwarding and just interrogate the combined logs as needed.

The second option is the easier to MAINTAIN and will cost you less effort in the long run.

When you start to solve a problem – stop and search for a bit to see if there is a solution already available in Windows server. Bet you’ll be surprised by what you find

October 31, 2016  10:38 AM

Start and end dates

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Be careful with start and end dates when search for files or through event logs.  Often people want to see what happened yesterday

If you do this

PS> $end =  Get-Date
PS> $start = (Get-Date).AddDays(-1)
PS> $start

30 October 2016 16:32:35
PS> $end

31 October 2016 16:32:16

And you get the last 24 hours

if you really want just yesterday you need to create the exact dates and times

PS> $start = Get-Date -Day 30 -Month 10 -Year 2016 -Hour 00 -Minute 00 -Second 00
PS> $end = Get-Date -Day 31 -Month 10 -Year 2016 -Hour 00 -Minute 00 -Second 00
PS> $start

30 October 2016 00:00:00
PS> $end

31 October 2016 00:00:00

I know there are easier ways but using Get-Date shows exact;y what’s happening and is simple

October 31, 2016  10:13 AM

Registration opens 1 November

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Registration for the 2017 PowerShell Summit opens tomorrow – 1 November 2016

First come first served. The agenda and registration are available here – https://eventloom.com/event/home/summit2017

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: