Iron Scripter Prelude 1 Solutions

So you had a little bit of time to work out a solution the first prelude challenge. Even though you might be tempted to answer all solutions as a script, sometimes you just need a command or two that will run successfully at a console prompt. Once you have the essential PowerShell commands working, then you can build a script or function around it. For the first prelude challenge most of you (hopefully) figured out a way to use the WindowsFeature cmdlets and Compare-Object. The Chairman has collected some code samples that are more or less representative of the different factions.

DayBreak

For clarity it might help to define variables for the source and target servers.

$source = "srv1"
$target = "srv2"

Using Get-WindowsFeature for the source computer would seem the obvious step. But here is more elegant approach.

$h = Get-WindowsFeature -ComputerName $source | Group-Object installed -AsString -AsHashTable

The challenge is to install features that are installed but missing on the target and remove features installed on the target not installed on the source. By grouping the features based on their installed property as a hashtable, it is easier to reference features.

Windows Features as a hashtable

You can use the hashtable to install missing features.

Get-WindowsFeature -Name $h.true.name -ComputerName $target | where {-not $_.installed} | Install-WindowsFeature -ComputerName $target -WhatIf

Installing missing Windows features

And likewise to remove features that shouldn’t be installed.

Get-WindowsFeature -Name $h.false.name -ComputerName $target | where {$_.installed} | Uninstall-WindowsFeature -ComputerName $target -WhatIf

Removing unwanted Windows Features

Battle

The Battle faction might take a more direct approach.

$s = "srv1"
$t = "srv2"
$a = Get-WindowsFeature -cn $s
$b = Get-WindowsFeature -cn $t
((diff $a $b -Property Name,Installed).where( {-not $_.installed -AND $_.sideindicator -eq "=>"})).name | add-windowsfeature -cn $t -WhatIf
((diff $a $b -Property Name,Installed).where( {$_.installed -AND $_.sideindicator -eq "=>"})).name | remove-windowsfeature -cn $t -WhatIf

Brutal but it gets the job done.

Flawless

The Flawless faction will invest a bit more time and might come up with a re-usable function like this:

function Set-WindowsFeature {
    [CmdletBinding(SupportsShouldProcess)]
    [alias("mirror")]
    param (
        [Parameter(Position = 0 , Mandatory, HelpMessage = "Enter the name of the source computer.")]
        [ValidateNotNullOrEmpty()]
        [string]$SourceComputer,
        [Parameter(Position = 1 , Mandatory, HelpMessage = "Enter the name of the target computer.")]
        [ValidateNotNullOrEmpty()]
        [string]$TargetComputer,
        [Parameter(HelpMessage = "Enter a credential that is good for both source and target computers.")]
        [PSCredential]$Credential,
        [Parameter(HelpMessage = "Install management tools")]
        [switch]$IncludeManagementTools,
        [Parameter(HelpMessage = "Install all subfeatures")]
        [switch]$IncludeAllSubFeature,
        [Parameter(HelpMessage = "Restart the target computer after all changes have been made.")]
        [switch]$Restart
    )
    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
        $getParams = @{
            ErrorAction  = "Stop"
            Computername = $SourceComputer
            Verbose      = $False
        }
        If ($Credential) {
            $getParams.Add("Credential", $Credential)
        }
        $addParams = @{
            Computername = $TargetComputer
            ErrorAction  = "Stop"
        }
        $removeParams = @{
            Computername = $TargetComputer
            ErrorAction  = "stop"
        }
        If ($IncludeManagementTools) {
            Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Including management tools"
            $addParams.Add("IncludeManagementTools", $True)
            $removeParams.Add("IncludeManagementTools", $True)
        }
        
        If ($IncludeAllSubFeature) {
            Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Including all subfeatures"
            $addParams.Add("IncludeAllSubFeature", $True)
        }
    } #begin
    process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Inventorying features on $($SourceComputer.toUpper())"
        Try {
            $installed = (Get-WindowsFeature @getParams).Where( {$_.installed})
        }
        Catch {
            Throw $_
        }
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Installing features on $($TargetComputer.toUpper())"
        $addParams.Name = $installed
        Try {
            Add-WindowsFeature @addParams
        }
        Catch {
            Throw $_
        }
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Removing features on $($TargetComputer.toUpper())"
        $getParams.Computername = $TargetComputer
        $installedTarget = (Get-WindowsFeature @getParams).Where( {$_.installed})
        $removeParams.name = ((Compare-Object -ReferenceObject  $installed -DifferenceObject $installedTarget -Property name, installed).Where( {$_.SideIndicator -eq '=>' -AND $_.installed})).name
        Try {
            Uninstall-WindowsFeature @removeParams
        }
        Catch {
            Throw $_
        }
    } #process
    End {
        if ($Restart) {
            Write-Verbose "[$((Get-Date).TimeofDay) END    ] Restarting $($TargetComputer.toUpper())"
            if ($Credential) {
                Restart-Computer -ComputerName $TargetComputer -credential $credential
            }
            else {
                Restart-Computer -ComputerName $TargetComputer
            }
        }
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end
}

The Flawless function in action

The Chairman hopes you find the sample solutions informative. Of course there are other ways to achieve the desired result for each faction. Hopefully you are sharing those results with your faction-mates.

Stay tuned for another Iron Scripter prelude challenge.


2 Replies to “Iron Scripter Prelude 1 Solutions”

  1. Francois

    Hi Jeff, I’ve decided to use the dism way directly on my side, not sure that it was a good choice but it works lol. Quick comment on your code, I think you forgot a -credential in your end block. Pretty nice code as usual btw ;).
    Cheers
    Francois

Comments are closed.