You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

352 lines
13 KiB

<#
.synopsis
Windows Trial lab management script
.parameter baseConfigName
The name of one of the subdirs like "windows_81_x86"
.parameter action
Which build actions do you want to perform?
.parameter tag
A tag for the temporary directory, the output directory, and the resulting Vagrant box
#>
[cmdletbinding()]
param(
[parameter(mandatory=$true,ParameterSetName="BuildPacker")]
[parameter(mandatory=$true,ParameterSetName="AddToVagrant")]
[parameter(mandatory=$true,ParameterSetName="VagrantUp")]
[string] $baseConfigName,
[parameter(mandatory=$true,ParameterSetName="DownloadWSUS")] [switch] $DownloadWSUS,
[parameter(mandatory=$true,ParameterSetName="ApplyWSUS")] [switch] $ApplyWSUS,
[parameter(mandatory=$true,ParameterSetName="BuildPacker")] [switch] $BuildPacker,
[parameter(mandatory=$true,ParameterSetName="AddToVagrant")] [switch] $AddToVagrant,
[parameter(mandatory=$true,ParameterSetName="VagrantUp")] [switch] $VagrantUp,
[parameter(mandatory=$true,ParameterSetName="ApplyWSUS")] [string] $isoPath,
[string] $baseOutDir = "D:\iso\wintriallab",
[string] $tempDirOverride,
[string] $tag,
[switch] $force,
[switch] $whatIf
)
# Module useful for Download-URL at least. TODO: this mixes concerns and may not be ideal?
ipmo $PSScriptRoot\scripts\postinstall\wintriallab-postinstall.psm1
$dateStamp = get-date -UFormat "%Y-%m-%d-%H-%M-%S"
$packerOutDir = "$baseOutDir\PackerOut"
$packerCacheDir = "$baseOutDir\packer_cache"
$wsusOfflineDir = "$baseOutDir\wsusoffline"
$labTempDir = "$baseOutDir\temp-$dateStamp"
if ($tempDirOverride) { $labTempDir = $tempDirOverride }
$wimMountDir = "${labTempDir}\MountInstallWim"
$errorActionPreference = "Stop"
#$fullConfigName = "wintriallab-${baseConfigName}-${dateStamp}"
$fullConfigName = "wintriallab-${baseConfigName}"
set-alias packer (gcm packer | select -expand path)
set-alias vagrant (gcm vagrant | select -expand path)
$outDir = "${packerOutDir}\${fullConfigName}"
if ($tag) { $outDir += "-${tag}"}
$packerConfigRoot = "${PSScriptRoot}\${baseConfigName}"
$packerFile = "${packerConfigRoot}\${baseConfigName}.packerfile.json"
$packedBoxPath = "${outDir}\${baseConfigName}_virtualbox.box"
$vagrantTemplate = "${packerConfigRoot}\vagrantfile-${baseConfigName}.template"
function Download-WSUSOfflineUpdater {
if (test-path $wsusOfflineDir) {
throw "WSUSOffline is already extracted to '$wsusOfflineDir'"
}
$filename = "wsusoffline101.zip"
$url = "http://download.wsusoffline.net/$filename"
$dlPath = "$labTempDir\$filename"
Get-WebUrl -url $url -downloadPath $dlPath
$exDir = resolve-path "$wsusOfflineDir\.." # why the .. ? because the zipfile puts everything in a 'wsusoffline' folder
sevenzip x "$dlPath" "-o$exDir"
}
function Download-WindowsUpdates {
set-alias DownloadUpdates "$wsusOfflineDir\cmd\DownloadUpdates.cmd"
foreach ($product in @('w63','w63-x64','w100','w100-x64')) {
DownloadUpdates $product glb /includedotnet /verify
}
}
function Test-AdminPrivileges {
$me = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
return $me.IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
}
function Get-DismWimInfo {
[cmdletbinding()] param (
[parameter(mandatory=$true)] [string] $wimfile
)
if (-not (Test-AdminPrivileges)) { throw "Admin privileges are required" }
write-verbose "Getting WIM information for file '$wimFile'"
$wimFile = resolve-path $wimFile | select -expand Path
$wimInfoText = Invoke-ExpressionAndCheck "dism /Get-WimInfo /WimFile:`"$wimfile`""
$wimIndexes = @()
$currentIndex = $false
foreach ($line in $wimInfoText) {
write-verbose $line
if ($line.startsWith("Index : ")) {
$currentIndex = New-Object PSObject
[int]$index = $line -replace "Index : ",""
Add-Member -inputObject $currentIndex -NotePropertyName "Index" -NotePropertyValue $index
Add-Member -inputObject $currentIndex -NotePropertyName "WimFile" -NotePropertyValue $wimFile
Add-Member -inputObject $currentIndex -MemberType ScriptProperty -Name DebugDesc -Value {
"$($this.WimFile) / $($this.Index) / $($this.Name)"
}
}
elseif ([String]::IsNullOrEmpty($line)) {
if ($currentIndex) { $wimIndexes += @($currentIndex) }
$currentIndex = $null
}
elseif ($currentIndex) {
$splitLine = [Regex]::Split($line, " : ")
Add-Member -inputObject $currentIndex -NotePropertyName $splitLine[0] -NotePropertyValue $splitLine[1]
}
}
return $wimIndexes
}
function Apply-WindowsUpdatesToWim {
[cmdletbinding()] param (
[parameter(mandatory=$true)] [string] $wimFile,
[parameter(mandatory=$true)] [string] $wimMountDir,
[parameter(mandatory=$true)] [string] $winUpdateDir
)
write-verbose "Applying Windows Updates to '$wimFile' from '$winUpdateDir'"
#Unblock-File $installWim
Set-ItemProperty -path $wimFile -name IsReadOnly -value $false -force
foreach ($wimInfo in (Get-DismWimInfo -wimFile $wimFile)) {
write-verbose "Attempging to apply WSUS Offline Updates to $($wimInfo.DebugDesc)"
$wimMountSubdir = mkdir "${wimMountDir}\$($wimInfo.Index)" -force | select -expand fullname
Invoke-ExpressionAndCheck "dism /mount-wim /wimfile:`"$installWim`" /mountdir:`"$wimMountSubdir`" /index:`"$($wimInfo.index)`""
if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { throw "External command failed with exit code '$LASTEXITCODE'" }
ls $updatePath\* -include *.cab |% {
write-verbose "Applying update at $_ to directory at $wimMountSubdir"
Invoke-ExpressionAndCheck "dism /image:`"$wimMountSubdir`" /add-package /packagepath:`"$_`""
}
Invoke-ExpressionAndCheck "dism /unmount-wim /mountdir:`"$wimMountSubdir`""
}
}
function Get-BootWimBitness($wimFile) {
$bootWimInfo = Get-DismWimInfo -wimFile $wimFile
if (-not $bootWimInfo) { throw "Got no information for wimfile at '$wimFile'"}
elseif ($bootWimInfo[0].Name -match "x86") { return "i386" }
elseif ($bootWimInfo[0].Name -match "x64") { return "amd64" }
else { throw "Could not determine WimBitness"}
}
function Get-WOShortCode($OSName, $OSBitness) {
$shortCodeTable = @{
"8.1" = "w63"
}
$shortCodeTable.keys |% { if ($OSName -match $_) { $shortCode = $shortCodeTable[$_] } }
if (-not $shortCode) { throw "Could not determine shortcode for an OS named '$OSName'" }
write-verbose "Found shortcode '$shortcode' for OS named '$OSName' of bitness '$bitness'"
if ($OSBitness -match "i386") { $shortCode += "" }
elseif ($OSBitness -match "amd64") { $shortCode += "-x64" }
else { throw "Could not determine shortcode for an OS of bitness '$OSBitness'" }
return $shortCode
}
function Apply-WindowsUpdatesToIso {
[cmdletbinding()] param (
[parameter(mandatory=$true)] [string] $iso,
[parameter(mandatory=$true)] [string] $wsusOfflineDir,
[parameter(mandatory=$true)] [string] $wimMountDir
)
$myWimMounts = @()
mount-diskimage -imagepath $iso
$mountedDrive = get-diskimage -imagepath $iso | get-volume | select -expand DriveLetter
$mountedDrive = get-diskimage -imagepath $iso | get-volume | select -expand DriveLetter
$installWim = cp "${mountedDrive}:\Sources\install.wim" $labTempDir -passthru | select -expand fullname
$bitness = Get-BootWimBitness -wimFile "${mountedDrive}:\sources\boot.wim"
dismount-diskimage -imagepath $iso
$wimInfo = Get-DismWimInfo -wimFile $installWim
$shortCode = Get-WOShortCode -OSName $wimInfo[0].Name -bitness $bitness
$updatePath = resolve-path "${wsusOfflineDir}\client\$shortCode\glb" | select -expand Path
Apply-WindowsUpdatesToWim -wimFile $installWim -wimMountDir $wimMountDir -winUpdateDir $updatePath
dismount-diskimage -imagepath $iso
}
function Build-PackerFile {
[cmdletbinding()]
param(
[parameter(mandatory=$true)] $packerFile,
[parameter(mandatory=$true)] $vagrantTemplate,
[parameter(mandatory=$true)] [string] $vagrantBoxName,
$tag,
$packerCacheDir,
$outDir,
[switch] $force,
[switch] $whatIf
)
$packerFile = get-item $packerFile
write-host $packerFile
if ($packerCacheDir) { $env:PACKER_CACHE_DIR = $packerCacheDir }
if (test-path $outDir) {
if ($force) { rm -force -recurse $outDir }
else { throw "Outdir already exists at '$outDir'" }
}
pushd (get-item $packerFile | select -expand fullname | split-path -parent)
try {
write-host "Building packer file '$($packerFile.fullname)' to directory '$outDir'..."
if (-not $whatif) {
packer build -var "output_directory=$outDir" "$($packerFile.fullname)"
if ($LASTEXITCODE -ne 0) { throw "External command failed with code $LASTEXITCODE" }
}
}
finally {
popd
}
$outBox = get-item $outDir\*.box
if ($outBox.count -gt 1) {
throw "Somehow you came up with more than one box here: '$outBox'"
}
if ($outBox -notmatch [Regex]::Escape($packedBoxPath)) {
throw "Found an output box '$outBox', but it doesn't match the expected packed box path of '$packedBoxPath'"
}
cp "$vagrantTemplate" "$outDir\Vagrantfile"
write-verbose "Packed .box file: '$packedBoxPath'"
}
function Add-BoxToVagrant {
[cmdletbinding()]
param(
[parameter(mandatory=$true)] $vagrantBoxName,
[parameter(mandatory=$true)] $packedBoxPath,
[switch] $force,
[switch] $whatIf
)
if (-not $whatIf) {
$forceOption = ""
if ($force) { $forceOption = "--force" }
vagrant box add $forceOption --name $vagrantBoxName $packedBoxPath
if ($LASTEXITCODE -ne 0) { throw "External command failed with code '$LASTEXITCODE'" }
}
}
function Run-VagrantBox {
[cmdletbinding()]
param(
[parameter(mandatory=$true)] $vagrantBoxName,
[parameter(mandatory=$true)] $workingDirectory, # with a Vagrantfile in it
[switch] $whatIf
)
if (-not $whatIf) {
try {
pushd $workingDirectory
vagrant up
if ($LASTEXITCODE -ne 0) { throw "External command failed with code '$LASTEXITCODE'" }
}
finally {
popd
}
}
}
function Show-LabVariable {
param(
[parameter(mandatory=$true)] [string] $varName,
[switch] $testPath
)
$LabVariable = new-object PSObject -Property @{
Variable = $varName
Value = get-variable $varName | select -expand value
PathExists = "-"
}
if ($testPath) { $LabVariable.PathExists = test-path $LabVariable.Value }
return $LabVariable
}
########
#$Basename = Get-Item $MyInvocation.MyCommand.Path | select -expand BaseName
#if ($MyInvocation.InvocationName -match $baseName) { # We were executed from the command line, not dot-sourced
mkdir -force -path $labTempDir | out-null
if ($baseConfigName) {
write-host ""
##write-output "Non-path variables: "
Show-LabVariable -varName 'fullConfigName'
##write-output "`nPaths to files that SHOULD exist already: "
Show-LabVariable packerConfigRoot -testPath
Show-LabVariable packerFile -testPath
Show-LabVariable vagrantTemplate -testPath
##write-output "`nPaths to files that SHOULD NOT exist (unless you passed -force): "
Show-LabVariable outDir -testPath
Show-LabVariable packedBoxPath -testPath
write-output ""
}
if ($DownloadWSUS) {
if (-not (test-path $wsusOfflineDir)) {
Download-WSUSOfflineUpdater
}
Download-WindowsUpdates
}
if ($ApplyWSUS) {
# Doing this different while in development
Apply-WindowsUpdatesToIso -iso $isoPath -wsusOfflineDir $wsusOfflineDir -wimMountDir $wimMountDir
<#
$installWim = resolve-path $labTempDir\install_81_x86.wim | select -expand path
$wimInfo = Get-DismWimInfo -wimFile $installWim
$bitness = "i386"
$shortCode = Get-WOShortCode -OSName $wimInfo[0].Name -OSBitness $bitness
$updatePath = resolve-path "${wsusOfflineDir}\client\$shortCode\glb" | select -expand Path
Apply-WindowsUpdatesToWim -Verbose -wimFile $installWim -wimMountDir $wimMountDir -winUpdateDir $updatePath
#>
throw "TODO: what about the cmdlets in 'gcm -module dism' ??"
throw "TODO: to convert this back to an iso, I have to have the WAIK and oscdimg.exe it looks like. Ugh."
}
if ($BuildPacker) {
$bpfParam = @{
packerFile = $packerFile
vagrantTemplate = $vagrantTemplate
vagrantBoxName = $fullConfigName
tag = $tag
packerCacheDir = $packerCacheDir
outDir = $outDir
force = $force
whatIf = $whatIf
}
Build-PackerFile @bpfParam
}
if ($AddToVagrant) {
Add-BoxToVagrant -vagrantBoxName $fullConfigName -packedBoxPath $packedBoxPath -force:$force -whatif:$whatif
}
if ($VagrantUp) {
Run-VagrantBox -vagrantBoxName $fullConfigName -workingDirectory $outDir -whatif:$whatif
}
#}