2011 Scripting Games warm-up. ;)

Yesterday Don Jones started short PowerShell challenge on his blog. More details here.

I decided to go short-and-quick way. Not sure if that’s the shortest way to do that (except for using aliases here and there) but I almost managed to finish all tasks in single pipe. It’s probably not most efficient, and also with requirement to use full names of cmdlets it’s just so long, that I had to break it in several lines each time to make it readable on my blog… 😉

Scripting Games are almost at our doors, so I treat it more like warm-up. Not expect to win, other will do it better for sure. But you know as it goes: reading input from others on the same problem is another thing that makes SG so exciting event: some parts of you brain start to kick in when you write your next script. 🙂 OK, let the code speak… 😉

1. Change login details for service on servers from single OU.

Using WMI, maybe not very clear because of number of parameters passed to Change() method:

(New-Object ADSISearcher $([ADSI]'LDAP://OU=Database Servers,DC=company,DC=com'), '(objectClass=Computer)').FindAll() |             
    ForEach-Object {            
        (Get-WmiObject -ComputerName $($_.Properties.Item('Name')) -Class Win32_Service -filter "Name='DataServer'").Change(            
            $null, $null, $null, $null, $null, $null,             
            'company\DataServers', 'P@$$w0rd',             
            $null, $null, $null) }

2. Table report about disks on file servers from list kept in FileServers.txt.

Since requirement was for a table report I use Format-Table here. Normally I avoid it functions/ scripts – but this is neither so here you go:

Get-WmiObject -Class Win32_LogicalDisk -ComputerName (Get-Content FileServers.txt) -filter "FreeSpace < $(20GB)" |             
    Format-Table @{ Name = 'Server Name'; Expression = { $_.__SERVER}},             
        @{ Name = 'Drive Letter'; Expression = { $_.DeviceID }},             
        @{ Name = 'Free (GB)'; FormatString = 'N2'; Expression = { $_.FreeSpace / 1GB}},             
        @{ Name = 'Disk Size (GB)'; FormatString = 'N2'; Expression = { $_.Size / 1GB}},            
        @{ Name = 'Percent Free'; FormatString = 'P0'; EXpression = { $_.FreeSpace/$_.Size }} -AutoSize            

I really would love to have @{n=’Name’;e={$_.Property};f=’n2’} here, but I assume it should be full also for that formatting hashtables. If not that would be much shorter… 😉

3. Inventory of workstations – exportable and easy to filter.

Now THAT is something I already did for myself. But it’s whole module, with types defined, the methods to filter stuff… SQL to store info almost done too. Wanted to do something similar here, so here is my version:

(New-Object ADSISearcher -Property @{             
    SearchRoot = [ADSI]''            
    Filter = '(objectClass=Computer)'            
    PageSize = 1000 } ).FindAll() |             
ForEach-Object -begin {            
    $PCList = New-Object Collections.ArrayList            
} -process {            
    $Name = $_.Properties.Item('Name')            
    if (Test-Connection -Quiet $Name -Count 1) {            
        $OS = Get-WmiObject -ComputerName $Name -Class Win32_OperatingSystem -Property BuildNumber, ServicePackMajorVersion            
        $PCList.Add($(            
        New-Object PSObject -Property @{            
            Name = [string]$Name            
            OSVersion = $OS.BuildNumber            
            SP = $OS.ServicePackMajorVersion            
            Serial = (Get-WmiObject -ComputerName $Name -Class Win32_Bios).SerialNumber             
        })) | Out-Null              
    } else {            
        $PCList.Add($(            
        New-Object PSObject -Property @{            
            Name = [string]$Name            
            OSVersion = 'Unknown'            
            SP = 'Unknown'            
            Serial = 'Unknown'            
        })) | Out-Null            
    }            
                    
} -end {            
    , $PCList | Add-Member -MemberType ScriptMethod -Name ToCSV -Value {             
            $this | Export-Csv -NoTypeInformation $args[0] } -PassThru |            
        Add-Member -MemberType ScriptMethod -Name ToHTML -Value {             
            $this | ConvertTo-Html | Out-File $args[0] } -PassThru |            
        Add-Member -MemberType ScriptMethod -Name ByOSVersion -Value {             
            param([string]$pattern) $this | where { $_.OSVersion -match $pattern } } -PassThru |            
        Add-Member -MemberType ScriptMethod -Name BySP -Value {             
            param([string]$pattern) $this | where { $_.SP -match $pattern } }            
}

Yes, that is technically one line… 😉 And you get $PCList object with methods .ToCsv([string]$Path); .ToHTML(‘[string]$Path); .ByOSVersion([string]$pattern) and .BySP([string]$pattern. Imagine you have it in your profile and now when you need info about PCs you just to $PClist.BySP(2) and get all PCs with Service Pack 2. 😉 Of course in production it requires some extra work, because it would get only online machines.

4. Create users from Users.csv and add them to Employees AD group.

At first I was tempted to use New-ADUser (with one select to change UserName to Name acceptable by cmdlet), but Don said: no loading. So there it is, ADSI solution:

Import-Csv Users.csv | Foreach-Object -begin {             
    $OU = [ADSI]'LDAP://CN=Users,DC=company,DC=com'            
    $Group = [ADSI]'LDAP://CN=Employees,CN=Users,DC=company,DC=com'            
} -process {            
    $User = $OU.Create('user',"CN=$($_.UserName)")            
    $User.Put('sAMAccountName',$_.UserName)            
    $User.Put('l',$_.City)            
    $User.Put('Department',$_.Department)            
    $User.Put('Title',$_.Title)            
    $User.SetInfo()            
    $Group.Add($User.ADSPath)            
}

As you see it’s all one-pipe events. I also managed to avoid end-line-backtick. I don’t like this technique because it fails miserably if someone copy it to his script and add space after grave accent. Newline is no longer escaped and it just breaks. And go figure, find reason for this error… 😉 When it all looks just fine! 😉