PowerShell for Windows Admins


March 22, 2017  1:43 PM

PowerShell Summit 2017–Community Lightning Demos

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

Early Summits, and their precursors – the PowerShell Deep Dives – featured Lightning rounds where attendees had 10 minutes or so to present something PowerShell related – a tip, trick, discovery, cool piece of code, new technique etc etc etc …

Summit 2017 brings that concept back. If you’re attending Summit, and if not why not, and you want to be involved check out https://powershell.org/2017/03/22/community-lightning-demos-call-for-proposals/ and sign up.

March 20, 2017  3:10 PM

Updating built in modules

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

Windows 10 and Server 2016 automatically install a module called Pester which is used for testing code. Its the foundation of Test Driven Development or Behaviour Driven Development using PowerShell.

The version  installed by default is 3.4.0.

Pester is originally an open source module that has been incorporated into Windows. The latest version from the PowerShell Gallery is 4.0.2

Normally you’d use Update-Module to install the new version BUT you didn’t install pester from the gallery using Install-Module so you’ll get a big fat error message.

The answer is to use

Install-Module pester –Force

You might still get an error message about the Pester module not being catalog signed. if you do and still want the latest version then use

Install-Module pester -Force -SkipPublisherCheck


March 10, 2017  2:49 PM

Hyper-V book deal

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Books, Hyper-V, Powershell

March 11 2017 – My book Learn Hyper-V in a Month of Lunches is Manning’s Deal of the day. Get 50% off using code dotd031117au at http://bit.ly/2niU715

Also see https://www.manning.com/dotd


March 6, 2017  11:53 AM

Windows 10 uptime

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell, Windows 10

One of the things that managers seem to be fascinated with is up time. For Windows server operating systems its a fairly simple calculation

PS>  (Get-Date) – (Get-CimInstance -ClassName Win32_OperatingSystem | select -ExpandProperty LastBootUpTime)

Days              : 0
Hours             : 3
Minutes           : 46
Seconds           : 45
Milliseconds      : 465
Ticks             : 136054659217
TotalDays         : 0.157470670390046
TotalHours        : 3.77929608936111
TotalMinutes      : 226.757765361667
TotalSeconds      : 13605.4659217
TotalMilliseconds : 13605465.9217

You can get a bit more precise by using the time the event log service is started as that’s early in the boot sequence while LastBootUpTime really records when the machine is ready to access. If you want to use the event log methodology see https://richardspowershellblog.wordpress.com/2009/02/14/machine-uptime/

With Windows 10 (actually with Windows 8 or 8.1 as well) this breaks down.

PS> (Get-Date) – (Get-CimInstance -ClassName Win32_OperatingSystem | select -ExpandProperty LastBootUpTime)

Days              : 23
Hours             : 6
Minutes           : 57
Seconds           : 55
Milliseconds      : 371
Ticks             : 20122753718697
TotalDays         : 23.2902242114549
TotalHours        : 558.965381074917
TotalMinutes      : 33537.922864495
TotalSeconds      : 2012275.3718697
TotalMilliseconds : 2012275371.8697

I use my computer a lot but 23 days is a bit excessive even for me.

If we look at the last boot time

PS> Get-CimInstance -ClassName Win32_OperatingSystem | select LastBootUpTime

LastBootUpTime
--------------
11/02/2017 10:00:35

You can see the calculation is correct.

This happens because Windows 10 (and 8 and 8.1) don’t fully power down when you do a Shut Down. They enter an extreme sleep state. This is the reason that start up times shrunk so dramatically when Windows 8 was introduced.

So how can you determine when your Windows 10, 8.1 or 8 machine was used.

You might be tempted to look in the System event log – where you’ll find entries like this each time the Windows machine is woken up

PS> Get-EventLog -LogName system -InstanceId 2147489661 | select -First 1 | Format-List


Index              : 8668
EntryType          : Information
InstanceId         : 2147489661
Message            : The system uptime is 1995412 seconds.
Category           : (0)
CategoryNumber     : 0
ReplacementStrings : {, , , ...}
Source             : EventLog
TimeGenerated      : 06/03/2017 12:17:24
TimeWritten        : 06/03/2017 12:17:24
UserName           :

Unfortunately this takes us back to out 23 days

PS> New-TimeSpan -Seconds 1995412


Days              : 23
Hours             : 2
Minutes           : 16
Seconds           : 52
Milliseconds      : 0
Ticks             : 19954120000000
TotalDays         : 23.0950462962963
TotalHours        : 554.281111111111
TotalMinutes      : 33256.8666666667
TotalSeconds      : 1995412
TotalMilliseconds : 1995412000

Using the event log service start up also gives us the hard power up date

PS> Get-EventLog -LogName system | where{(($_.EventId -eq 6005) -or ($_.EventId -eq 6006)) } | select -First 1 | Format-
List


Index              : 6275
EntryType          : Information
InstanceId         : 2147489653
Message            : The Event log service was started.
Category           : (0)
CategoryNumber     : 0
ReplacementStrings : {}
Source             : EventLog
TimeGenerated      : 11/02/2017 10:00:30
TimeWritten        : 11/02/2017 10:00:30
UserName           :

There is an entry in the System event log for when the machine comes out of its power down mode:

PS> Get-EventLog -LogName system -InstanceId 1 | where Message -Like '*low power*' | select -First 1 | Format-List


Index              : 8683
EntryType          : Information
InstanceId         : 1
Message            : The system has returned from a low power state.

                     Sleep Time: 2017-03-05T22:00:54.261033400Z
                     Wake Time: 2017-03-06T12:17:26.089189400Z

                     Wake Source: 0
Category           : (0)
CategoryNumber     : 0
ReplacementStrings : {2017-03-05T22:00:54.261033400Z, 2017-03-06T12:17:26.089189400Z, 1245, 1590...}
Source             : Microsoft-Windows-Power-Troubleshooter
TimeGenerated      : 06/03/2017 12:17:28
TimeWritten        : 06/03/2017 12:17:28
UserName           : NT AUTHORITY\LOCAL SERVICE

so you could calculate uptime as

PS> (Get-Date) - (Get-EventLog -LogName system -InstanceId 1 | where Message -Like '*low power*' | select -First 1 | sel
ect -ExpandProperty TimeGenerated)


Days              : 0
Hours             : 4
Minutes           : 54
Seconds           : 9
Milliseconds      : 174
Ticks             : 176491748155
TotalDays         : 0.20427285666088
TotalHours        : 4.90254855986111
TotalMinutes      : 294.152913591667
TotalSeconds      : 17649.1748155
TotalMilliseconds : 17649174.8155

If you really wanted to you could dig into the log entries and use the sleep and wake times in the message block

PS> Get-EventLog -LogName system -InstanceId 1 | where Message -Like '*low power*' | select -First 1 | select -ExpandPro
perty ReplacementStrings
2017-03-05T22:00:54.261033400Z
2017-03-06T12:17:26.089189400Z
1245
1590
1501
0
9469
5143
195417
16641
6
5
0
0

0
0
0
PS> $rs = Get-EventLog -LogName system -InstanceId 1 | where Message -Like '*low power*' | select -First 1 | select -ExpandProperty ReplacementStrings

From which you can calculate the time the machine was powered down (asleep)

PS> [datetime]$rs[1] - [datetime]$rs[0]


Days              : 0
Hours             : 14
Minutes           : 16
Seconds           : 31
Milliseconds      : 828
Ticks             : 513918281560
TotalDays         : 0.59481282587963
TotalHours        : 14.2755078211111
TotalMinutes      : 856.530469266667
TotalSeconds      : 51391.828156
TotalMilliseconds : 51391828.156

So how much have I been using the machine in the last week

$now = Get-Date
$start = $now.AddDays(-7)

$events = Get-EventLog -LogName system -InstanceId 1 -After $start  | 
where Message -Like '*low power*'

$lastevt = $events.Count - 1 


## time since last wake up
$usetime = $now - [datetime]$events[0].ReplacementStrings[1]

for ($i=0; $i -lt $lastevt; $i++){
 
  $Sleep = [datetime]$events[$i].ReplacementStrings[0]
  $Wake = [datetime]$events[$i+1].ReplacementStrings[1]
  
  $tt = $sleep - $wake

  $usetime += $tt

}

$usetime
Days              : 2
Hours             : 15
Minutes           : 46
Seconds           : 20
Milliseconds      : 736
Ticks             : 2295807363007
TotalDays         : 2.65718444792477
TotalHours        : 63.7724267501944
TotalMinutes      : 3826.34560501167
TotalSeconds      : 229580.7363007
TotalMilliseconds : 229580736.3007

Calculating Windows 10 uptime isn’t simple but can be done with these techniques


March 4, 2017  10:31 AM

Full Summit agenda available

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

The full agenda – including the PowerShell team sessions – is now available on the event web site – – https://eventloom.com/event/home/summit2017

This is our biggest ever Summit. We’ve sold out for this year and are already making plans for next year’s Summit. Look for more information later in the year.


March 4, 2017  5:59 AM

Modifying AD users in bulk

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

Modifying AD users in bulk involves either setting one or more properties to the same value for a set of users or reading in the values you need from a data source of some kind.

We prepared some test data in the last post so lets see how we use it.

$users = Import-Csv -Path .\users.csv
foreach ($user in $users){
 Get-ADUser -Identity $user.Id |
 Set-ADUser -Division $user.Division -EmployeeNumber $user.EmployeeNumber
}

The simplest way is to read in the data and store as a collection of objects. Use foreach to iterate through the set of user information. Get-ADUser gets the appropriate AD account which is piped to Set-ADUser. Set-ADUser is a great cmdlet because it has parameters for most of the user properties.

In this case though we know that some of the users don’t have employee numbers. This means a bit more work. Two approaches are possible – use splatting and the parameters used above or use the –Replace option

Lets look at splatting first

$users = Import-Csv -Path .\users.csv  
foreach ($user in $users){
 $params = @{
   Division = $user.Division
   EmployeeNumber = 0
 }
 
 if ($user.EmployeeNumber) {
   $params.EmployeeNumber = $user.EmployeeNumber
 }
 else {
   $params.Remove('EmployeeNumber')
 }
 
 Get-ADUser -Identity $user.Id |
 Set-ADUser @params
}

As before read the user information into the $users variable. Iterate over the users with foreach. Create a hashtable for the parameters and their values. Division is always present so that can be set directly. Employeenumber should be tested and if  present the place holder value should be overwritten with the correct value otherwise Employeenumber is removed from the hashtable.

The user account is found and Set-ADUser sets the correct values. Notice how the hashtable is specified to the cmdlet.

Splatting is a great way to dynamically set the parameters you’re using on a particular cmdlet.

Set-ADUser has an alternative – the –Replace parameter.

$users = Import-Csv -Path .\users.csv 
foreach ($user in $users){
 $params = @{
   division = $user.Division
   employeeNumber = 0
 }
 
 if ($user.EmployeeNumber) {
   $params.EmployeeNumber = $user.EmployeeNumber
 }
 else {
   $params.Remove('EmployeeNumber')
 }
 
 Get-ADUser -Identity $user.Id |
 Set-ADUser -Replace $params
}

This is very similar to the splatting example but instead of splatting the hashtable you use it as the value input to the Replace parameter. If you wrote  out the command it would look like this:

 Set-ADUser –Replace @{division = ‘Division B’; employeeNumber  = 100}

With –Replace you’re using the LDAP names of the properties rather than the GUI or PowerShell name – there are differences for instance surname is sn in LDAP.

Modifying AD users in bulk is straightforward with PowerShell and its relatively easy to deal with missing values if you adopt one of the above ideas. Splatting is probably the easiest in this case.

 


March 3, 2017  1:52 PM

Test data for bulk AD changes

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

I’ve had a number of questions about changing AD user data in bulk. If you need to do that you need some test data. The specific questions were around setting the Division property and the EmployeeNumber at the same time – but some accounts didn’t have an employee number.

First you need to generate some test data

$count =  1

Get-ADUser -Filter * -SearchBase 'OU=UserAccounts,DC=Manticore,DC=org' |
foreach {
  
  $props = @{
    Id = $psitem.SamAccountName
    Division = ''
    EmployeeNumber = $count
  }


  switch ($count % 3){
    0 {$props.Division = 'Division A'}
    1 {$props.Division = 'Division B'}
    2 {$props.Division = 'Division C'}
  }

  if (-not ($count % 15)){$props.Remove('EmployeeNumber')}
  
  New-Object -TypeName PSObject -Property $props

  $count ++
} |
Export-Csv -Path users.csv –NoTypeInformation

You need a counter variable. Use Get-ADUser to get you test users and pipe to foreach. You can then create a hashtable for the properties – Id = samaccountname, Division is an empty string and EmployeeNumber is set equal to count.

A switch statement is used to modify the division. Modulo 3 on the $count variable can only give values of 0, 1 or 2.

Remove the EmployeeNumber for every 15th account

Create an object. Increment the counter.

Export the objects to a csv file.  You should end up with something like this:

Division   EmployeeNumber Id
--------   -------------- --
Division B 1              DonJones
Division C 2              DonSmith
Division A 3              DonBrown
Division B 4              DonBlack
Division C 5              DonWhite
Division A 6              DonGreen
Division B 7              DonWood
Division C 8              DonBell
Division A 9              DonHarris
Division B 10             DonFox
Division C 11             JamesJones
Division A 12             JamesSmith
Division B 13             JamesBrown
Division C 14             JamesBlack
Division A                JamesWhite
Division B 16             JamesGreen
Division C 17             JamesWood

etc.

Next time I’ll show you how to deal with the missing employee numbers when you modify the AD accounts.


March 1, 2017  6:05 AM

Get-Content and Numbers

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

A common technique is to put a list of information into a text file and read that using Get-Content. The information is often server names. This works great when the data is strings but breaks down if you’re dealing with numbers.

Lets start with a text file containing some numbers:

PS> Get-Content -Path .\num.txt
1000
109
258
331
699
744
829

Let’s try and sort those numbers:

PS> Get-Content -Path .\num.txt | sort
1000
109
258
331
699
744
829

It may seem that nothing is happening but if you sort descending:

PS> Get-Content -Path .\num.txt | sort -Descending
829
744
699
331
258
109
1000

Things change but not quite in the way that you might think. The data isn’t being sorted numerically its being sorted as strings.

PS> Get-Content -Path .\num.txt | Get-Member


   TypeName: System.String

If you read the help file for Get-Content it states that the cmdlet returns either strings or bytes depending on the content of the file.

So if Get-Content returns strings what can we do if we want to work with the file contents as numbers rather than text.

You have a few choices. You can force a type conversion (known in programming circles as a cast) in a number of ways.

First using the –as operator

PS> Get-Content -Path .\num.txt | foreach {$psitem -as [int]} | sort
109
258
331
699
744
829
1000

That’s more like a numeric sort

You can cut down on the code:

PS> Get-Content -Path .\num.txt | foreach {[int]$psitem} | sort
109
258
331
699
744
829
1000

Or you can use foreach to call  a method on the object

PS> Get-Content -Path .\num.txt | foreach ToInt32($psitem) | sort
109
258
331
699
744
829
1000

You can even use the foreach method on the array that Get-Content creates

PS> (Get-Content -Path .\num.txt).foreach({[int]$psitem})  | sort
109
258
331
699
744
829
1000

The last option is the fastest but is only available on PowerShell 4.0 and above.

Get-Content returns strings but its not difficult to turn them into the numbers you need

 

 


February 24, 2017  4:25 AM

Wanted

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

Wanted one nice kind PowerShell community member to buy the last lonely ticket for the 2017 PowerShell and DevOps Summit.

That’s right we have one place left. And its definitely the last. if you want it – better hurry.


February 16, 2017  2:24 PM

Get-ADUser doesn’t display all properties

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

Microsoft’s Active Directory cmdlets have some issues. One of the ones that catches everyone when they start using them is that Get-ADUser doesn’t display all properties.

A default call to Get-ADUser displays a subset of the available properties of the user object:

DistinguishedName : CN=FOX Fred,OU=UserAccounts,DC=Manticore,DC=org
Enabled           : True
GivenName         :
Name              : FOX Fred
ObjectClass       : user
ObjectGUID        : db5a3975-980d-4749-b9c0-48aff9217b2a
SamAccountName    : foxfred
SID               : S-1-5-21-759617655-3516038109-1479587680-1314
Surname           :
UserPrincipalName : FredFox@manticore.org

Even if the properties are empty – such as Givenname and Surname – the property name is displayed. So, how do you get the properties that aren’t part of the default list?

There’s the brute force approach:

PS> Get-ADUser -Identity foxfred -Properties *


AccountExpirationDate                :
accountExpires                       : 9223372036854775807
AccountLockoutTime                   :
AccountNotDelegated                  : False
AllowReversiblePasswordEncryption    : False
AuthenticationPolicy                 : {}
AuthenticationPolicySilo             : {}
BadLogonCount                        : 0
badPasswordTime                      : 0
badPwdCount                          : 0
CannotChangePassword                 : False
CanonicalName                        : Manticore.org/UserAccounts/FOX Fred
Certificates                         : {}
City                                 :
CN                                   : FOX Fred
codePage                             : 0
Company                              :
CompoundIdentitySupported            : {}
Country                              :
countryCode                          : 0
Created                              : 17/11/2016 14:07:13
createTimeStamp                      : 17/11/2016 14:07:13
Deleted                              :
Department                           :
Description                          :
DisplayName                          :
DistinguishedName                    : CN=FOX Fred,OU=UserAccounts,DC=Manticore,DC=org
Division                             :
DoesNotRequirePreAuth                : False
dSCorePropagationData                : {01/01/1601 00:00:00}
EmailAddress                         :
EmployeeID                           :
EmployeeNumber                       :
Enabled                              : True
Fax                                  :
GivenName                            :
HomeDirectory                        :
HomedirRequired                      : False
HomeDrive                            :
HomePage                             :
HomePhone                            :
Initials                             :
instanceType                         : 4
isDeleted                            :
KerberosEncryptionType               : {}
LastBadPasswordAttempt               :
LastKnownParent                      :
lastLogoff                           : 0
lastLogon                            : 0
LastLogonDate                        :
LockedOut                            : False
logonCount                           : 0
LogonWorkstations                    :
Manager                              :
MemberOf                             : {}
MNSLogonAccount                      : False
MobilePhone                          :
Modified                             : 18/11/2016 11:03:02
modifyTimeStamp                      : 18/11/2016 11:03:02
msDS-User-Account-Control-Computed   : 8388608
Name                                 : FOX Fred
nTSecurityDescriptor                 : System.DirectoryServices.ActiveDirectorySecurity
ObjectCategory                       : CN=Person,CN=Schema,CN=Configuration,DC=Manticore,DC=org
ObjectClass                          : user
ObjectGUID                           : db5a3975-980d-4749-b9c0-48aff9217b2a
objectSid                            : S-1-5-21-759617655-3516038109-1479587680-1314
Office                               :
OfficePhone                          :
Organization                         :
OtherName                            :
PasswordExpired                      : True
PasswordLastSet                      : 17/11/2016 14:07:13
PasswordNeverExpires                 : False
PasswordNotRequired                  : False
POBox                                :
PostalCode                           :
PrimaryGroup                         : CN=Domain Users,CN=Users,DC=Manticore,DC=org
primaryGroupID                       : 513
PrincipalsAllowedToDelegateToAccount : {}
ProfilePath                          :
ProtectedFromAccidentalDeletion      : False
pwdLastSet                           : 131238652330182673
SamAccountName                       : foxfred
sAMAccountType                       : 805306368
ScriptPath                           :
sDRightsEffective                    : 15
ServicePrincipalNames                : {}
SID                                  : S-1-5-21-759617655-3516038109-1479587680-1314
SIDHistory                           : {}
SmartcardLogonRequired               : False
State                                :
StreetAddress                        :
Surname                              :
Title                                :
TrustedForDelegation                 : False
TrustedToAuthForDelegation           : False
UseDESKeyOnly                        : False
userAccountControl                   : 512
userCertificate                      : {}
UserPrincipalName                    : FredFox@manticore.org
uSNChanged                           : 78123
uSNCreated                           : 62259
whenChanged                          : 18/11/2016 11:03:02
whenCreated                          : 17/11/2016 14:07:13

Using –properties * returns ALL of the properties of a user. That’s OK if you’re looking at one, or a few users, but becomes a very expensive operation if you’re looking at thousands of user objects.

A more elegant approach is to specify the properties you want:

PS> Get-ADUser -Identity foxfred -Properties EmailAddress, LockedOut, ProtectedFromAccidentalDeletion, whenCreated


DistinguishedName               : CN=FOX Fred,OU=UserAccounts,DC=Manticore,DC=org
EmailAddress                    :
Enabled                         : True
GivenName                       :
LockedOut                       : False
Name                            : FOX Fred
ObjectClass                     : user
ObjectGUID                      : db5a3975-980d-4749-b9c0-48aff9217b2a
ProtectedFromAccidentalDeletion : False
SamAccountName                  : foxfred
SID                             : S-1-5-21-759617655-3516038109-1479587680-1314
Surname                         :
UserPrincipalName               : FredFox@manticore.org
whenCreated                     : 17/11/2016 14:07:13

You get the properties you specified and the default properties.

So, while Get-ADUser doesn’t display all properties you can overcome this by using the –properties parameter with a * for all properties or a list of the properties you want in addition to the defaults.


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: