PowerShell for Windows Admins


September 9, 2013  12:11 PM

Solving partial displays of AD properties



Posted by: Richard Siddaway
Active Directory, PowerShell

Had an interesting question today. The question concerned displaying the StreetAddress property when its multiple lines like this:

PS> Get-ADUser -Identity dgreen -Properties StreetAddress | fl Name, StreetAddress

Name : GREEN Dave
StreetAddress : Floor 5
Buidling 3
Newboro Square

If you try this with format-table instead

PS> Get-ADUser -Identity dgreen -Properties StreetAddress | ft Name, StreetAddress -a

Name StreetAddress
—- ————-
GREEN Dave Floor 5…

The address is truncated

The answer is to use the –Wrap parameter

PS> Get-ADUser -Identity dgreen -Properties StreetAddress | ft Name, StreetAddress -a -Wrap

Name StreetAddress
—- ————-
GREEN Dave Floor 5
Buidling 3
Newboro Square

And all is good

September 9, 2013  11:23 AM

Exercise in frustration



Posted by: Richard Siddaway
Rant

If you would like an exercise in frustration – try installing the RSAT tools for Windows 2012 onto Windows 8. If your PS’s language is set to en-US it should work. If it is anything else give up now and go watch the grass grow – it will be infinitely more rewarding.

Many people outside of the US want to administer Windows Servers from their PC – why is it so hard? Especially on an English system!!!

You have to go through so many hoops to try and get the language pack installed – its just not worth the effort. I can spin up a Windows 2012 VM to do my admin quicker!


September 9, 2013  2:04 AM

Getting WMI data from remote machines



Posted by: Richard Siddaway
PowerShell 3, WMI

WMI is great for pulling back data from remote machines. This type of activity is quite common:

$computer = $env:COMPUTERNAME

$comp = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computer
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computer
$disk = Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computer

New-Object -TypeName PSObject -Property @{
Name = $computer
OS = $os.Caption
LastReBoot = $os.LastBootUpTime
Type = “$($comp.Manufacturer) : $($comp.Model)”
AvailableRAM = $comp.TotalPhysicalMemory
Disks = ($disk | Measure).Count
}

Define the computer. Run some WMI queries & pull the data together into a single object for output.

Each time you call Get-WmiObject you have to rebuild the connection to the remote machine. There appears to be a little bit of caching of information so its not a complete rebuild of the connection but the system has to do some work.

For a few calls to WMI its not a big deal but what you need to make more calls – say 8 or 10.

The CIM cmdlets introduced the concept of CIM sessions for this scenario. Bu the CIM cmdlets work over WSMAN by default & if you have machines running legacy versions of PowerShell or even no PowerShell you can’t use CIM sessions.

Oh yes you can! (sorry but pantomime season is rapidly approaching)

You just configure you CIM session to work over DCOM – just like WMI cmdlets.

$computer = $env:COMPUTERNAME
$copt = New-CimSessionOption -Protocol Dcom
$csess = New-CimSession -SessionOption $copt -ComputerName $computer

$comp = Get-CIMInstance -ClassName Win32_ComputerSystem -CimSession $csess
$os = Get-CIMInstance -ClassName Win32_OperatingSystem -CimSession $csess
$disk = Get-CIMInstance -ClassName Win32_LogicalDisk -CimSession $csess

$csess | Remove-CimSession

New-Object -TypeName PSObject -Property @{
Name = $computer
OS = $os.Caption
LastReBoot = $os.LastBootUpTime
Type = “$($comp.Manufacturer) : $($comp.Model)”
AvailableRAM = $comp.TotalPhysicalMemory
Disks = ($disk | Measure).Count
}

The session option is defined with the DCOM protocol

The Get-CimInstance calls use –CimSession instead of –ComputerName

Best of all the LastBootUpTime is converted to a proper date

If you’re thinking of using multiple WMI calls to a remote machine – think about a CIM session


September 8, 2013  7:28 AM

How much RAM does my machine have?



Posted by: Richard Siddaway
PowerShell 3, WMI

If you need to determine the RAM in a system you can use the Win32_ComputerSystem class

Get-CimInstance -ClassName Win32_ComputerSystem |
select Name, TotalPhysicalMemory

The answer is in bytes BUT it only shows the memory available to the OS. Many systems steal RAM for graphics cards or other devices.

The true amount of physical memory is obtained from the Win32_PhysicalMemory class which gets data in each memory bank in your system. PowerShell will even add them up for you if you ask nicely.

Get-WmiObject Win32_PhysicalMemory |
Measure-Object -Property Capacity –Sum

If you just need the value:

(Get-WmiObject Win32_PhysicalMemory |
Measure-Object -Property Capacity -Sum).Sum

or

Get-WmiObject Win32_PhysicalMemory |
Measure-Object -Property Capacity -Sum |
select -ExpandProperty Sum


September 7, 2013  9:49 AM

Bitten by a typo



Posted by: Richard Siddaway
PowerShell

Got caught by a silly typing mistake that caused a bit of head scratching until I tracked it down.

A common scenario is adding an element $y to an array $x

$x += $y

I’d typed

$x =+= $y

The function loaded but failed at run time.

Took a while to spot.


September 6, 2013  2:45 PM

Cleaning up my AD



Posted by: Richard Siddaway
Active Directory, PowerShell

I decided it was time to clean some of the rubbish out of my test AD. I’ll be upgrading to Windows Server 2012 R2 next month so a bi tof a clean up now is a good idea.

I decided to start with the computer objects. I’ve created & deleted quite a few virtual machines over the years so there’s a good chance of finding something to remove. Computes in an AD domain have a secure channel to the domain controller to which they authenticate on startup. The password on this channel is reset automatically every 30 days. Any machines that haven’t reset their password in a while a probably good candidtes for removal:

Get-ADComputer -Filter * -Properties PasswordLastSet |
select Name, PasswordLastSet |
sort PasswordLastSet

That shows me a few machines to remove. Anything that hasn’t reset its password for 12 months is fair game.

$date = (Get-Date).AddYears(-1)
Get-ADComputer -Filter {PasswordLastSet -lt $date} -Properties PasswordLastSet |
select Name, PasswordLastSet | sort PasswordLastSet

Its odd but I couldn’t get the search to work when I was calculating the date in the filter

Now I can delete them:

PS> Get-ADComputer -Filter {PasswordLastSet -lt $date} -Properties PasswordLastSet | Remove-ADComputer -Confirm:$false

Remove-ADComputer : The directory service can perform the requested operation only on a leaf object
At line:1 char:82
+ … swordLastSet | Remove-ADComputer -Confirm:$false
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (CN=W08SQL05,OU=…anticore,DC=org:ADComputer) [Remove-ADComputer], ADExce
ption
+ FullyQualifiedErrorId : ActiveDirectoryServer:8213,Microsoft.ActiveDirectory.Management.Commands.RemoveADComputer

Not what I was expecting. The error message is what you get when trying to delete an OU with objects still in it but a computer object is a leaf object.

It turns out that the computer object can contain other objects especially when its a virtual machine. Unfortunately, the only way to see this is to use ADSIEdit. This is the full ADSIedit you need not the Attribute Editor in AD Users & Computers or AD Administrative Center. When I looked in ADSIEdit I saw there was indeed a child object

CN=Windows Virtual Machine,CN=W08SQL05,OU=SQL Server,OU=Servers,DC=Manticore,DC=org

Both of the affected machines were Windows 2000 VMs but later versions of Windows up to and including Windows 2012 are also affected.

So how to delete:

Option 1 – use the GUI and force deletion. Who me? Not likely. Smile

Option 2 – use Remove-ADObject

Get-ADComputer -Filter {PasswordLastSet -lt $date } |
Remove-ADObject -Recursive -Verbose -Confirm:$false

That’s computers cleaned up. Just leaves users, groups & OUs


September 6, 2013  2:18 AM

Multiple services on multiple servers–another way



Posted by: Richard Siddaway
PowerShell 3

A potentially simpler way to solve the issue of multiple servers and multiple services is to use a hash table

$data = @{
“server02″ = “BITS”, “NtFrs”, “MSMQ”, “Kdc”;
“exch10″ = “MSExchangeAB”, “W32Time”, “W3SVC”
}

foreach ($server in $data.Keys){
Get-Service -ComputerName $server -Name ($data[$server]) |
select @{N=”Server”; E={$server}}, Status, Name, DisplayName
}

The data structure is a bit more intuitive as you can see the direct link between the server and its list of services.

A version using PSObjects is available on the forum – http://powershell.org/wp/forums/topic/help-to-output-array/


September 5, 2013  3:38 PM

Getting remote services



Posted by: Richard Siddaway
PowerShell 3

Getting the service on a remote server is easy

Get-Service -ComputerName Exch10

Getting a set of services on a remote machine isn’t difficult

Get-Service -ComputerName Exch10 -Name “MSExchangeAB”, “W32Time”, “W3SVC”

Ok so what about the scenario with multiple servers

Get-Service -ComputerName Exch10, server02

Multiple servers with a set of servers just means adding the –Name parameter and the list of services

Get-Service -ComputerName Exch10, server02 -Name “W32Time”, “W3SVC”

but you have to have the same list of services for each server.

Now lets get really picky and go for multiple servers where each one has a different set of services.

I thought about csv files but then how do you repent the list? Nested arrays – yuck.

I ended up with this approach. Its a bit messy but works and is easily expandable & changeable

$servers = “server02″, “exch10″
$server02_services = “BITS”, “NtFrs”, “MSMQ”, “Kdc”
$exch10_services = “MSExchangeAB”, “W32Time”, “W3SVC”

foreach ($server in $servers){
Get-Service -ComputerName $server -Name (Get-Variable -Name ($server + “_services”)).value |
select @{N=”Server”; E={$server}}, Status, Name, DisplayName
}

Create a list of servers. Create a list of services per server. Notice the naming convention for the variables.

Iterate over the servers using foreach. The server name comes from the foreach iteration variable. The list of services is created by using get-variable. Substitute the server name to create the variable name and use the Value property to give you the services of interest. Use select to add the computer name to the output.

I don’t use the *Variable cmdlets very often and this was a neat use of get-variable


September 5, 2013  2:46 PM

Discovering a users OU



Posted by: Richard Siddaway
Active Directory, PowerShell

Interesting question – how do you discover the OU in which an AD user is sitting? The Quest cmdlets were very helpful because they had a ParentContainer property. With the Microsoft cmdlets you have to do a bit of work

There are two places to look – the distinguished name and the canonical name

PS> $user = Get-ADUser -Identity Richard -Properties Canonicalname
PS> $user

CanonicalName : Manticore.org/Users/Richard
DistinguishedName : CN=Richard,CN=Users,DC=Manticore,DC=org
Enabled : True
GivenName : Richard
Name : Richard
ObjectClass : user
ObjectGUID : b94a5255-28d0-4f91-ae0f-4c853ab92520
SamAccountName : Richard
SID : S-1-5-21-3881460461-1879668979-35955009-1104
Surname :
UserPrincipalName : Richard@Manticore.org

Notice the different formats

The distinguished name is easiest

PS> ($user.DistinguishedName -split “,”, 2)[1]
CN=Users,DC=Manticore,DC=org

use split on the DistinguishedName. Note the format of the split command – - – “,”, 2

It means split on a comma and give me two elements – one containing the data before the first comma & the second containing all data after the first comma

The canonical name needs a bit more work

PS> $elements = $user.CanonicalName -split ‘/’
PS> $elements[0..($elements.Count - 2)] -join ‘/’
Manticore.org/Users

split the canonical name on ‘/’ and then recreate the string dropping the last element


September 4, 2013  2:26 PM

Integer sizes save the day



Posted by: Richard Siddaway
PowerShell

I was looking to convert some string data into integers – the data was bytes & I needed it in GB. The value I needed was buried in the middle of a string so I needed to do some string processing. Some other conditions forced me to use the Parse method of the integer class

Lets say I get a number like 2147483169 back in the data & I need to divide by 1gb

£> [int]::Parse(“2147483169″) / 1gb
1.99999955389649

I’ve put the string value directly in here rather than the complicated string processing I was actually using.

The point came when I realised that I could have much bigger numbers than 2Gb so what was the maximum I could work with

£> [int]::maxValue
2147483647
£> [int]::maxValue / 1gb
1.99999999906868

OK thats not enough. The standard [int] is 32bits & I know its big brother has 64 bits so how big is big brother

£> [int64]::maxValue
9223372036854775807
£> [int64]::maxValue / 1gb
8589934592

That’s more than big enough so I went with 64 bit integers.

This isn’t just a developer type thing because I was working with Exchange mail box sizes. So the moral of the story is to think about the possible values you’ll see in you data while you are coding to avoid errors or failure when you are running the script.


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: