PowerShell for Windows Admins


July 29, 2015  7:30 AM

Windows 10

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Windows 10

Went through the Windows 10 upgrade procedure this morning. Fairly quick process – just over the hour including download on a slow broadband link. Most of my settings from Windows 8.1 were saved.

 

So far impressions are reasonable to good.

 

PowerShell 5.0 is installed

£> $PSVersionTable

Name                           Value
—-                           —–
PSVersion                      5.0.10240.16384
WSManStackVersion              3.0
SerializationVersion           1.1.0.1
CLRVersion                     4.0.30319.42000
BuildVersion                   10.0.10240.16384
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3

 

The Edge browser (IE replacement) is missing functionality for managing favourites and doesn’t appear to incorporaet an RSS feed reader. IE is still available – hunt through program files to get to the exe.

 

The Mail app doesn’t supply a unified view across all mailboxes.

 

The big improvement over Windows 8/8.1 is that you get a unified view of applications. No more metro style or desktop. Everything runs on the desktop though the lack of a left and bottom border on the PowerShell console is weird.

 

So far nothing leaps out as a show stopper

July 28, 2015  11:23 AM

PowerShell Summit NA 2016–call for topics warning

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

A conference organiser’s work is never done. The dust has just settled from PowerShell Summit NA 2015 and we’re gearing up for PowerShell Europe 2015.

We also need to start thinking about PowerShell NA 2016! A post on powershell.org

http://powershell.org/wp/2015/07/28/powershell-summit-na-2016-call-for-topics-coming-soon/

serves as a warning to start thinking about topics for PowerShell NA 2016 which will be in Bellevue (Seattle) again. The official call for topics will go out next week all being well.

Don’t worry about PowerShell Europe 2016 – we haven’t forgotten about that – the call for topics will occur towards the end of the year.


July 28, 2015  5:26 AM

WMI dates

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

Dates as reported by WMI still seem to cause a lot of problems. If you use the WMI cmdlets

£> Get-WmiObject -Class Win32_OperatingSystem | select *date* | fl
InstallDate   : 20131205101649.000000+000
LocalDateTime : 20150728121320.002000+060

 

That format is year, month, day, hour, minute, second then fractions of a second after the decimal point with the final +nnn indicating an offset from Greenwich Mean Time  (UTC) for time zones and daylight saving time.

You can read the date presented by WMI but its not intuitive.

The PowerShell team added a ConvertToDateTime() to the object output by WMI so that you can easily perform date conversions

£> Get-WmiObject -Class Win32_OperatingSystem | select @{N=’Install'; E={$_.ConvertToDateTime($_.Installdate)}}, @{N=’Lo
calDate'; E={$_.ConvertToDateTime($_.LocalDateTime)}} | fl
Install   : 05/12/2013 10:16:49
LocalDate : 28/07/2015 12:16:26

 

Though my preferred solution these days is to use the CIM cmdlets as they convert the date for you without any extra effort

£> Get-CimInstance -ClassName Win32_OperatingSystem | select *date* | fl
InstallDate   : 05/12/2013 10:16:49
LocalDateTime : 28/07/2015 12:17:29


July 25, 2015  7:44 AM

Self signed certificates for testing

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

A question on the forum

http://powershell.org/wp/forums/topic/cannot-add-digital-signature-to-my-script/#post-27883

indicated a problem when using a self signed certificate for testing code signing.

According to the about_signing help file

CREATE A SELF-SIGNED CERTIFICATE
——————————–
To create a self-signed certificate in use the New-SelfSignedCertificate
cmdlet in the PKI module. This module is introduced in Windows PowerShell
3.0 and is included in Windows 8 and Windows Server 2012. For more
information, see the help topic for the New-SelfSignedCertificate cmdlet.

To create a self-signed certificate in earlier versions of Windows, use
the Certificate Creation tool (MakeCert.exe). This  tool is included in
the Microsoft .NET Framework SDK (versions 1.1 and later) and in the
Microsoft Windows SDK.

However the cert produced by New-SelfSifgnedCertificate only appears to function as a SSL self signed cert. It isn’t accepted as a code signing cert.

You can still get the makecert utility for Windows 8.1 from

https://msdn.microsoft.com/en-gb/windows/desktop/bg162891.aspx

and Windows 8 from

https://msdn.microsoft.com/en-us/library/windows/desktop/hh852363.aspx

The makecert utility can be found in

C:\Program Files (x86)\Windows Kits\8.1\bin\x64
or

C:\Program Files (x86)\Windows Kits\8.1\bin\x86

for the 64 & 32bit versions respectively

While you shouldn’t use self-signed certs for production they are useful for testing. My recommendation is to use the makecert utility rather than the PKI cmdlet

 


July 23, 2015  11:26 AM

Devops Tools

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
DevOps

DevOps is the fusion of development and operations practices to give you faster, more reliable, roll out of functionality to your organisation.  Its a growing area and while much of DevOps practice is cultural rather than technical you will need at least some tools to get the job done.

There are a growing number of tools available and finding what you need can be difficult. I stumbled across this site:

http://www.devopsbookmarks.com/

which functions as an aggregation point for a significant number of the current tools. Even if you don’t need them now its worth knowing what’s available.

Don’t forget PowerShell and in particular Desired State Configuration in all of this though.


July 22, 2015  8:23 AM

PowerShell documentation and more

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

PowerShell has a new home.  This site on MSDN https://msdn.microsoft.com/en-us/powershell is the centre for Microsoft’s PowerShell documentation. Book mark it now!


July 22, 2015  8:04 AM

Using parameters instead of read-host when getting AD replication data

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

I’ve seen a lot of scripts recently that use Read-Host to get input data.  This is generally not best practice – I tend to only use Read-Host if I want to get a password and obscure the text on screen.

A better practice is to use parameters – either in a function or a script. As an example consider this function that gets AD replication metadata

function get-ADReplmetadata {
param (
[Parameter(Mandatory=$true)]
[string]$ldapfilter,

[Parameter(Mandatory=$true)]
[string]$attribute,

[string]$server = ‘server02′
)
Get-ADObject -LDAPFilter “($ldapfilter)”  -Properties $attribute |
Get-ADReplicationAttributeMetadata -Server $server -Attribute $attribute

}

Get-ADReplicationAttributeMetadata  is awkward to use because it only accepts a distinguished name or a GUID for identifying the object you want to access. Remembering distinguished names or GUIDs  is a pain so I use get-AdObject with an LDAP filter and pipe it to Get-ADReplicationAttributeMetadata .

The $server parameter defaults to server02 but can be overridden if you want to use another domain controller

I make the ldapfilter and attributes mandatory so I get prompted if I forget

This example pulls back meta data for just the Name

get-ADReplmetadata -ldapfilter ‘samAccountName=Richard’ -attribute Name

This example pulls back all metadata

get-ADReplmetadata -ldapfilter ‘samAccountName=Richard’ -attribute *


July 21, 2015  2:54 PM

Data for comparisons

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

A question on the forum asked about storing data used in comparisons, The example was based on a list if IP addresses where some were known to be good and the questioner wanted to filter out the known good ones so he just had to investigate the rest.

You could put the data in your script

Remove-Item -Path C:\TestScripts\unknownip.txt -Force
$safe = ‘127.0.0.1’,
‘10.10.54.199’,
‘10.10.54.200’,
‘192.168.0.1’
$ips = ‘127.0.0.1’, ‘10.10.54.199’, ‘172.16.5.1’, ‘192.168.0.47’, ‘10.10.54.200’

foreach ($ip in $ips){
if ($ip -notin $safe) {
Out-File -InputObject $ip -FilePath C:\TestScripts\unknownip.txt -Append
}
}

cat C:\TestScripts\unknownip.txt

The advantage is that you only maintain one file. The disadvantage come sif you want to use the data in another script.

Another approach is to put the data in  a file

127.0.0.1
10.10.54.199
10.10.54.200
192.168.0.1

I saved it as safeips.txt

The script changes to

Remove-Item -Path C:\TestScripts\unknownip.txt -Force
$safe = Get-Content -Path C:\TestScripts\safeips.txt

$ips = ‘127.0.0.1’, ‘10.10.54.199’, ‘172.16.5.1’, ‘192.168.0.47’, ‘10.10.54.200’

foreach ($ip in $ips){
if ($ip -notin $safe) {
Out-File -InputObject $ip -FilePath C:\TestScripts\unknownip.txt -Append
}
}

cat C:\TestScripts\unknownip.txt

In production you’d obviously read the IPs to test from a file of some kind rather than hard code in the script.

The second approach involves maintaining 2 files but gives greater flexibility – its the approach I prefer

 

 

 


July 20, 2015  12:55 PM

Input validation on multiple regex

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell, Regular expressions

One of the things I like about writing advanced functions is the ability to validate the input. if you test the input immediately you can  often stop mistakes being made. Mistakes that could damage your system!

One of the validation options is to test the input against a regular expression. Not being a big fan of regular expressions I don’t use this often but one option that came up on the forum was testing against more than one regex.

DISCLAIMER – The regex I’m using aer for illustrative purposes only and I don’t claim they are the best way of doing this.

This function uses a simple regex to validate the input starts with a letter:

function Test-Pval
{
[CmdletBinding()]
Param
(
[ValidatePattern(“^[a-z]”)]
[String]
$parm1
)
Write-Host “Input was $parm1″
}

£> Test-Pval abcd
Input was abcd

£> Test-Pval Abcd
Input was Abcd

£> Test-Pval 2bcd
Test-Pval : Cannot validate argument on parameter ‘parm1′. The argument “2bcd” does not match the “^[a-z]” pattern.
Supply an argument that matches “^[a-z]” and try the command again.
At line:1 char:11
+ Test-Pval 2bcd
+           ~~~~
+ CategoryInfo          : InvalidData: (:) [Test-Pval], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Test-Pval

 

Lets also assume that we want to be able to accept input starting with @ or #

function Test-Pval
{
[CmdletBinding()]
Param
(
#[ValidatePattern(“^[a-z]”)]
[ValidatePattern(“^[@,#]”)]
[String]
$parm1
)
Write-Host “Input was $parm1″
}

£> Test-Pval 2bcd
Test-Pval : Cannot validate argument on parameter ‘parm1′. The argument “2bcd” does not match the “^[@,#]” pattern.
Supply an argument that matches “^[@,#]” and try the command again.
At line:1 char:11
+ Test-Pval 2bcd
+           ~~~~
+ CategoryInfo          : InvalidData: (:) [Test-Pval], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Test-Pval
£> Test-Pval abcd
Test-Pval : Cannot validate argument on parameter ‘parm1′. The argument “abcd” does not match the “^[@,#]” pattern.
Supply an argument that matches “^[@,#]” and try the command again.
At line:1 char:11
+ Test-Pval abcd
+           ~~~~
+ CategoryInfo          : InvalidData: (:) [Test-Pval], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Test-Pval
£> Test-Pval @bcd
Test-Pval : Cannot validate argument on parameter ‘parm1′. The argument “” does not match the “^[@,#]” pattern.
Supply an argument that matches “^[@,#]” and try the command again.
At line:1 char:11
+ Test-Pval @bcd
+           ~~~~
+ CategoryInfo          : InvalidData: (:) [Test-Pval], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Test-Pval
£> Test-Pval ‘@bcd’
Input was @bcd

£> Test-Pval ‘#bcd’
Input was #bcd

£> Test-Pval ‘?bcd’
Test-Pval : Cannot validate argument on parameter ‘parm1′. The argument “?bcd” does not match the “^[@,#]” pattern.
Supply an argument that matches “^[@,#]” and try the command again.
At line:1 char:11
+ Test-Pval ‘?bcd’
+           ~~~~~~
+ CategoryInfo          : InvalidData: (:) [Test-Pval], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Test-Pval

 

The previous acceptable input is now rejected and only input starting with @ or # is accepted.

To combine the 2 patterns we use the | symbol which is an OR operator in regex

function Test-Pval
{
[CmdletBinding()]
Param
(
[ValidatePattern(“^[a-z | @,#]”)]
[String]
$parm1
)
Write-Host “Input was $parm1″
}

function Test-Pval
{
[CmdletBinding()]
Param
(
[ValidatePattern(“^[a-z | @,#]”)]
[String]
$parm1
)
Write-Host “Input was $parm1″
}

Which when used give this

£> Test-Pval abcd
Input was abcd

£> Test-Pval Abcd
Input was Abcd

£> Test-Pval 2bcd
Test-Pval : Cannot validate argument on parameter ‘parm1′. The argument “2bcd” does not match the “^[a-z | @,#]”
pattern. Supply an argument that matches “^[a-z | @,#]” and try the command again.
At line:1 char:11
+ Test-Pval 2bcd
+           ~~~~
+ CategoryInfo          : InvalidData: (:) [Test-Pval], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Test-Pval
£> Test-Pval ‘@bcd’
Input was @bcd

£> Test-Pval ‘#bcd’
Input was #bcd

£> Test-Pval ‘?bcd’
Test-Pval : Cannot validate argument on parameter ‘parm1′. The argument “?bcd” does not match the “^[a-z | @,#]”
pattern. Supply an argument that matches “^[a-z | @,#]” and try the command again.
At line:1 char:11
+ Test-Pval ‘?bcd’
+           ~~~~~~
+ CategoryInfo          : InvalidData: (:) [Test-Pval], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Test-Pval

Expect to spend a long time figuring out the regex though


July 17, 2015  12:37 PM

get-computerDN–dealing with non-existant computers

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

If you look at the working part of the code to discover the distinguished name of a computer:

 

£> $computer = ‘w12r2sus’
£> $filter = “(&(objectCategory=computer)(objectClass=computer)(cn=$computer))”
£> ([adsisearcher]$filter).FindOne().Properties.distinguishedname
CN=W12R2SUS,OU=Servers,DC=Manticore,DC=org

 

What happens if the computername you chose doesn’t exist?

 

£> $computer = ‘w12r2nope’
£> $filter = “(&(objectCategory=computer)(objectClass=computer)(cn=$computer))”
£> ([adsisearcher]$filter).FindOne().Properties.distinguishedname
£>

 

You don’t get anything returned

 

The filter is formed correctly

£> $filter
(&(objectCategory=computer)(objectClass=computer)(cn=w12r2nope))

 

The FindOne() method doesn’t return anything

 

£> ([adsisearcher]$filter).FindOne()
£>

 

If you’re just working with a single computer then not getting a result is a fair indication of a problem but if you’ve passed a number of  computer names to the function you need to know easily which ones aren’t present – especially if you save the results in a collection and want to do something else with them.

 

In this case I’d use write-warning to output a message that the machine wasn’t found:

 

£> $computer = ‘w12r2nope’
£> $filter = “(&(objectCategory=computer)(objectClass=computer)(cn=$computer))”
£> $result = ([adsisearcher]$filter).FindOne()
£>
£> if ($result) {
>> $result.Properties.distinguishedname
>> }
>> else {
>>  Write-Warning -Message “Computer not found: $computer”
>> }
>>
WARNING: Computer not found: w12r2nope
£>

 

Putting that code into yesterday’s function gives us:

 

function get-computerDN {
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact=”Low”)]

param (
[parameter(Position=0,
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage=”Supply computer name” )]
[Alias(“CN”, “Computer”)]
[ValidateNotNullOrEmpty()]
[string[]]$computername
)
BEGIN{}#begin
PROCESS{

foreach ($computer in $computername) {
if ($psCmdlet.ShouldProcess(“$computer”, “Retreiving distinguished name”)) {
$filter = “(&(objectCategory=computer)(objectClass=computer)(cn=$computer))”
$result = ([adsisearcher]$filter).FindOne()

if ($result) {
$result.Properties.distinguishedname
}
else {
Write-Warning -Message “Computer not found: $computer”
}

} # end if ($psCmdlet.ShouldProcess(“$computer”, “Retreiving distinguished name”))
} # end foreach ($computer in $computername) {

}#process
END{}#end

<#
.SYNOPSIS
Returns distinguished name of a computer

.DESCRIPTION
Returns distinguished name of one or more computers in the domain.
Assumes connectivity to domain controller. Domain independent.

.PARAMETER  computername
Name of computer for which distinguished name will be returned

.EXAMPLE
get-computerDN -computername server02

Returns the distinguished name for server02.

Results are of the form:
CN=SERVER02,OU=Domain Controllers,DC=Manticore,DC=org

.NOTES
[adsisearcher] is a shortcut for System.DirectoryServices.DirectorySearcher
.LINK

#>

}

 

Which is used like this

£> ‘server02′, ‘w12r2sus’, ‘w12r2nope’ | get-computerDN
CN=SERVER02,OU=Domain Controllers,DC=Manticore,DC=org
CN=W12R2SUS,OU=Servers,DC=Manticore,DC=org
WARNING: Computer not found: w12r2nope

 

One thing you need to keep in mind when estimating the time you’ll take to write a script – you’ll be writing at least half as much again validation, error handling and help/comments compared to the actual working code


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: