PowerShell for Windows Admins


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


July 16, 2015  12:47 PM

get-computerDN function

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

Last time I showed a bare bones function for using [adsisearcher] to retrieve the distinguished name of a domain computer.

This is a somewhat expanded version of that function which adds in the sorts of features you’d want for a production ready function.

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))”
([adsisearcher]$filter).FindOne().Properties.distinguishedname
}
}

}#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

#>

}

 

[CmdletBinding()] adds –whatif and other standard parameters. It should be always part of a production script.

The param block is expanded to make the parameter mandatory, work with the pipeline and supply a help message if the value isn’t given

A couple of aliases for the parameter are defined and the parameter can’t be Null or empty

The parameter also accepts an array of values rather than a single value.

The PROCESS block is used for the actual working code. A foreach loop is used to iterate through the computernames and the retrieval of the distinguished name is wrapped in a if statement to allow for –whatif scenarios

Finally some help is defined.  I always add help tot he end of function so that its out of the way when I’m working on the code. It spersonal preference. If you want to put your help somewhere else – feel free.

Using the function gives you more options:

Simple single computer:£> get-computerDN -computername server02
CN=SERVER02,OU=Domain Controllers,DC=Manticore,DC=org

Whatif:

£> get-computerDN -computername server02 -WhatIf
What if: Performing the operation “Retreiving distinguished name” on target “ser
ver02″.

multiple machines

£> get-computerDN -computername server02, w12r2sus
CN=SERVER02,OU=Domain Controllers,DC=Manticore,DC=org
CN=W12R2SUS,OU=Servers,DC=Manticore,DC=org

computername as positional parameter

£> get-computerDN server02, w12r2sus
CN=SERVER02,OU=Domain Controllers,DC=Manticore,DC=org
CN=W12R2SUS,OU=Servers,DC=Manticore,DC=org

parameter aliases

£> get-computerDN -computer server02, w12r2sus
CN=SERVER02,OU=Domain Controllers,DC=Manticore,DC=org
CN=W12R2SUS,OU=Servers,DC=Manticore,DC=org

£> get-computerDN -cn server02, w12r2sus
CN=SERVER02,OU=Domain Controllers,DC=Manticore,DC=org
CN=W12R2SUS,OU=Servers,DC=Manticore,DC=org

Help

£> Get-Help get-computerDN

NAME
get-computerDN

SYNOPSIS
Returns distinguished name of a computer

SYNTAX
get-computerDN [-computername] <String[]> [-WhatIf] [-Confirm]
[<CommonParameters>]

DESCRIPTION
Returns distinguished name of one or more computers in the domain.
Assumes connectivity to domain controller. Domain independent.
RELATED LINKS
REMARKS
To see the examples, type: “get-help get-computerDN -examples”.
For more information, type: “get-help get-computerDN -detailed”.
For technical information, type: “get-help get-computerDN -full”.
For online help, type: “get-help get-computerDN -online”

 

Full help
£> Get-Help get-computerDN -Full

NAME
get-computerDN

SYNOPSIS
Returns distinguished name of a computer

SYNTAX
get-computerDN [-computername] <String[]> [-WhatIf] [-Confirm]
[<CommonParameters>]

DESCRIPTION
Returns distinguished name of one or more computers in the domain.
Assumes connectivity to domain controller. Domain independent.
PARAMETERS
-computername <String[]>
Name of computer for which distinguished name will be returned

Required?                    true
Position?                    1
Default value
Accept pipeline input?       true (ByValue, ByPropertyName)
Accept wildcard characters?  false

-WhatIf [<SwitchParameter>]

Required?                    false
Position?                    named
Default value
Accept pipeline input?       false
Accept wildcard characters?  false

-Confirm [<SwitchParameter>]

Required?                    false
Position?                    named
Default value
Accept pipeline input?       false
Accept wildcard characters?  false

<CommonParameters>
This cmdlet supports the common parameters: Verbose, Debug,
ErrorAction, ErrorVariable, WarningAction, WarningVariable,
OutBuffer, PipelineVariable, and OutVariable. For more information,
see
about_CommonParameters
(http://go.microsoft.com/fwlink/?LinkID=113216).

INPUTS

OUTPUTS

NOTES

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

————————– EXAMPLE 1 ————————–

PS C:\>get-computerDN -computername server02

Returns the distinguished name for server02.

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

RELATED LINKS
mandatory parameter
£> get-computerDN
cmdlet get-computerDN at command pipeline position 1
Supply values for the following parameters:
(Type !? for Help.)
computername[0]:
get-computerDN : Cannot validate argument on parameter ‘computername’. The
argument is null, empty, or an element of the argument collection contains a
null value. Supply a collection that does not contain any null values and then
try the command again.
At line:1 char:1
+ get-computerDN
+ ~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidData: (:) [get-computerDN], ParameterBind
ingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,get-computerDN
Help message on mandatory parameter

£> get-computerDN
cmdlet get-computerDN at command pipeline position 1
Supply values for the following parameters:
(Type !? for Help.)
computername[0]: !?
Supply computer name
computername[0]:

Pipeline input

£> ‘server02′, ‘w12r2sus’ | get-computerDN
CN=SERVER02,OU=Domain Controllers,DC=Manticore,DC=org
CN=W12R2SUS,OU=Servers,DC=Manticore,DC=org

One thing missing is error handling – I’ll cover that next time


July 15, 2015  1:54 PM

ADSIsearcher returns

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

I’ve been using the Microsoft AD cmdlets (and before that the Quest cmdlets) for so long that I’d sort of forgotten about [adsisearcher].

It was introduced in PowerShell 2.0 and is a shortcut for System.DirectoryServices.DirectorySearcher

A question in the forum about using this remotely made me realise that many people have probably never used it before – and to think that 5 years ago it was the way to go – how things change.

The question revolved around using this code

$filter = “(&(objectCategory=computer)(objectClass=computer)(cn=$env:COMPUTERNAME))”
([adsisearcher]$filter).FindOne().Properties.distinguishedname

for other machines.

An attempt was made to use Invoke-Command but that won’t work because you are attempting to delegate you credentials to make the call to AD – and that’s not allowed under the default configuration for remoting. You also can’t guarantee that remoting is enabled on older machines.

All you have to do is replace $env:COMPUTERNAME with the name of the computer for which you want to get the distinguished name.  Easiest way to do this is with a function

function get-computerDN {
param ($computername)
$filter = “(&(objectCategory=computer)(objectClass=computer)(cn=$computername))”
([adsisearcher]$filter).FindOne().Properties.distinguishedname

}

This is an absolute bare bones function just to show the way the parameter is used – you should validate the input and add some error handling as basic improvements.

£> get-computerDN -computername server02
CN=SERVER02,OU=Domain Controllers,DC=Manticore,DC=org

Next time I’ll show how to take the basic functionality and create something a bit more robust


July 14, 2015  12:05 PM

Disk identification

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell, WMI

A recent question on the forums regarded using the Win32_LogicalDisk class to retrieve disk size and free space data.  A filter based on the disk letter was being used. The main error with the code was that the filter was being expressed as

“DeviceId=’E'”

rather than

“DeviceId=’E:”

The colon is necessary as its part of the DeviceId data – if you are in doubt about the form of the data required by the filter then examine the full output of the class to see an example.

There were a couple of other basic issues.

Firstly always output objects.

Secondly use the size constants MB, GB etc rather than calculating refresh each time.

The final modified code looks like this

$computername = $env:COMPUTERNAME
$partition = ‘C:’
$description = ‘backup_server’

Get-WmiObject -Class Win32_LogicalDisk -Filter “DeviceId=’$partition'”     -ComputerName $computername |
select PSComputerName,
@{Name=’Partition'; Expression={$_.DeviceId}},
@{Name=’Description'; Expression={$description}},
@{Name=’Size(GB)';Expression={[math]::Round(($_.Size / 1GB), 2)}},
@{Name=’FreeSpace(GB)';Expression={[math]::Round(($_.FreeSpace / 1GB), 2)}}


July 11, 2015  4:36 AM

PowerShell and messaging

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Messaging, MSMQ, Powershell, Windows Server 2012 R2

This article – http://powershell.org/wp/2015/07/07/rabbitmq-and-powershell/ – reminded me that Windows server comes with a built-in messaging system – MSMQ.

There is a PowerShell module for MSMQ – https://technet.microsoft.com/en-us/library/hh405007(v=vs.85).aspx

Since reading the RabbitMQ article I think that Rabbit is more useful in a heterogeneous environment but MSMQ would work very nicely in a Windows only environment.

I’ve not looked at MSMQ for a long time and not seen much on using it through PowerShell so I think its time to remedy that – I’ll put together a short series on using MSMQ through PowerShell to complement the RabbitMQ information


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: