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.
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
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
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 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.
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
Good catch on the missing credential. I started the code but never finished it. I’ve updated the article.