<# jesus fucking christ fucking Packer TODO: - make every function 100% reliant on itself only. - get rid of calls to Get-LabTempDir - decided whether I'm using $URLs or not lol #> ### Global Constants that I use elsewhere $ArchitectureId = @{ amd64 = "amd64" i386 = "i386" } $WindowsVersionId = @{ w63 = "w63" # TODO: rename to w81 probably w10 = "w10" w10ltsb = "w10ltsb" server2012r2 = "server2012r2" } $OfficeVersionId = @{ o2013 = "o2013" } $IsoUrls = @{ $WindowsVersionId.w63 = @{ $ArchitectureId.i386 = "http://care.dlservice.microsoft.com/dl/download/B/9/9/B999286E-0A47-406D-8B3D-5B5AD7373A4A/9600.17050.WINBLUE_REFRESH.140317-1640_X86FRE_ENTERPRISE_EVAL_EN-US-IR3_CENA_X86FREE_EN-US_DV9.ISO" $ArchitectureId.amd64 = "http://care.dlservice.microsoft.com/dl/download/B/9/9/B999286E-0A47-406D-8B3D-5B5AD7373A4A/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_ENTERPRISE_EVAL_EN-US-IR3_CENA_X64FREE_EN-US_DV9.ISO" } $WindowsVersionId.w10 = @{ $ArchitectureId.i386 = "http://care.dlservice.microsoft.com/dl/download/C/3/9/C399EEA8-135D-4207-92C9-6AAB3259F6EF/10240.16384.150709-1700.TH1_CLIENTENTERPRISEEVAL_OEMRET_X86FRE_EN-US.ISO" $ArchitectureId.amd64 = "http://care.dlservice.microsoft.com/dl/download/C/3/9/C399EEA8-135D-4207-92C9-6AAB3259F6EF/10240.16384.150709-1700.TH1_CLIENTENTERPRISEEVAL_OEMRET_X64FRE_EN-US.ISO" } $WindowsVersionId.w10ltsb = @{ $ArchitectureId.i386 = "http://care.dlservice.microsoft.com/dl/download/6/2/4/624ECF83-38A6-4D64-8758-FABC099503DC/10240.16384.150709-1700.TH1_CLIENTENTERPRISE_S_EVAL_X86FRE_EN-US.ISO" $ArchitectureId.amd64 = "http://care.dlservice.microsoft.com/dl/download/6/2/4/624ECF83-38A6-4D64-8758-FABC099503DC/10240.16384.150709-1700.TH1_CLIENTENTERPRISE_S_EVAL_X64FRE_EN-US.ISO" } $WindowsVersionId.server2012r2 = @{ $ArchitectureId.amd64 = "http://care.dlservice.microsoft.com/dl/download/6/2/A/62A76ABB-9990-4EFC-A4FE-C7D698DAEB96/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_SERVER_EVAL_EN-US-IR3_SSS_X64FREE_EN-US_DV9.ISO" } $OfficeVersionId.o2013 = @{ $ArchitectureId.i386 = "http://care.dlservice.microsoft.com/dl/download/2/9/C/29CC45EF-4CDA-4710-9FB3-1489786570A1/OfficeProfessionalPlus_x86_en-us.img" $ArchitectureId.amd64 = "http://care.dlservice.microsoft.com/dl/download/2/9/C/29CC45EF-4CDA-4710-9FB3-1489786570A1/OfficeProfessionalPlus_x64_en-us.img" } } $WSUSCatalogUrl = "http://download.windowsupdate.com/microsoftupdate/v6/wsusscan/wsusscn2.cab" $WSUSOfflineRepoBaseUrl = "https://svn.wsusoffline.net/svn/wsusoffline/trunk" $szUrl = "http://7-zip.org/a/$szFilename" $URLs = @{ ISOs = @{ $WindowsVersionId.w63 = @{ $ArchitectureId.i386 = "http://care.dlservice.microsoft.com/dl/download/B/9/9/B999286E-0A47-406D-8B3D-5B5AD7373A4A/9600.17050.WINBLUE_REFRESH.140317-1640_X86FRE_ENTERPRISE_EVAL_EN-US-IR3_CENA_X86FREE_EN-US_DV9.ISO" } } WindowsUpdateCatalog = "http://download.windowsupdate.com/microsoftupdate/v6/wsusscan/wsusscn2.cab" WSUSOfflineRepoBase = "https://svn.wsusoffline.net/svn/wsusoffline/trunk" SevenZipDownload = @{ $ArchitectureId.i386 = "http://7-zip.org/a/7z920.msi" $ArchitectureId.amd64 = "http://7-zip.org/a/7z920-x64.msi" } UltraDefragDownload = @{ $ArchitectureId.i386 = "http://downloads.sourceforge.net/project/ultradefrag/stable-release/6.1.0/ultradefrag-portable-6.1.0.bin.i386.zip" $ArchitectureId.amd64 = "http://downloads.sourceforge.net/project/ultradefrag/stable-release/6.1.0/ultradefrag-portable-6.1.0.bin.amd64.zip" } SdeleteDownload = "http://download.sysinternals.com/files/SDelete.zip" } ### Private support functions I use behind the scenes function Get-WebUrl { [cmdletbinding(DefaultParameterSetName="outDir")] param( [parameter(mandatory=$true)] [string] $url, [parameter(mandatory=$true,ParameterSetName="outDir")] [string] $outDir, [parameter(mandatory=$true,ParameterSetName="outFile")] [string] $outFile ) if ($PScmdlet.ParameterSetName -match "outDir") { $filename = [System.IO.Path]::GetFileName($url) $outFile = "$outDir\$filename" } $outFile = [IO.Path]::GetFullPath($outFile) (New-Object System.Net.WebClient).DownloadFile($url, $outFile) return (get-item $outFile) } function Invoke-ExpressionAndCheck { [cmdletbinding()] param( [parameter(mandatory=$true)] [string] $command, [int] $sleepSeconds ) $global:LASTEXITCODE = 0 write-verbose "Invoking expression '$command'" invoke-expression -command $command write-verbose "Expression '$command' had a last exit code of '$LastExitCode'" if ($global:LASTEXITCODE -ne 0) { throw "LASTEXITCODE: ${global:LASTEXITCODE} for command: '${command}'" } if ($sleepSeconds) { start-sleep $sleepSeconds } } # TODO: Copy-ItemAndExclude # function Copy-ItemAndExclude { # [cmdletbinding()] param( # [parameter(mandatory=$true)] [string] $path, # [parameter(mandatory=$true)] [string] $destination, # [parameter(mandatory=$true)] [string[]] $exclude, # [switch] $force # ) # $path = resolve-path $path | select -expand path # $sourceItems = Get-ChildItem -Path $path -Recurse -Exclude $exclude # write-verbose "Found $($sourceItems.count) items to copy from '$path'" # #$sourceItems | copy-item -force:$force -destination {Join-Path $destination $_.FullName.Substring($path.length)} # $sourceItems | copy-item -force:$force -destination { # if ($_.GetType() -eq [System.IO.FileInfo]) { # Join-Path $destination $_.FullName.Substring($path.length) # } # else { # Join-Path $destination $_.Parent.FullName.Substring($path.length) # } # } # } function Apply-XmlTransform { [cmdletbinding()] param( [parameter(mandatory=$true)] [string] $xmlFile, [parameter(mandatory=$true)] [string] $xsltFile, [parameter(mandatory=$true)] [string] $outFile ) $xmlFile = resolve-path $xmlFile | select -expand path $xsltFile = resolve-path $xsltFile | select -expand path if (test-path $outFile) { throw "outFile exists at '$outFile'" } $outParent = split-path -parent $outFile | resolve-path | select -expand Path $outName = split-path -leaf $outFile $outFile = "$outParent\$outName" $xslt = New-Object System.Xml.Xsl.XslCompiledTransform $xslt.Load($xsltFile) $xslt.Transform($xmlFile, $outFile) return (get-item $outFile) } <# .description Get the path of the Windows ADK or AIK or whatever the fuck they're calling it from a format string - {0} is always the WAIK directory - e.g. "C:\Program Files (x86)\Windows Kits\8.1\" - e.g. "X:\Program Files\Windows Kits\8.0" - {1} is always the host architecture (x86 or amd64) - i THINK this is right, but I don't understand WHY. why do you need an amd64 version of oscdimg.exe? - however, there are arm executables lying around, and i definitely can't execute those. wtf? So we expect a string like "{0}\bin\{1}\wsutil.exe" #> function Get-AdkPath { [cmdletbinding()] param( [parameter(mandatory=$true)] [string] $pathFormatString ) $adkPath = "" $possibleAdkPaths = @("${env:ProgramFiles(x86)}\Windows Kits\8.1","${env:ProgramFiles}\Windows Kits\8.1") $possibleAdkPaths |% { if (test-path $_) { $adkPath = $_ } } if (-not $adkPath) { throw "Could not find the Windows Automated Installation Kit" } write-verbose "Found the WAIK at '$adkPath'" $arch = Get-OSArchitecture switch ($arch) { $ArchitectureId.i386 { $formatted = $pathFormatString -f $adkPath,$waikArch if (test-path $formatted) { return $formatted } } $ArchitectureId.amd64 { foreach ($waikArch in @("amd64","x64")) { $formatted = $pathFormatString -f $adkPath,$waikArch if (test-path $formatted) { return $formatted } } } default { throw "Could not determine architecture of '$arch'" } } throw "Could not resolve format string '$pathFormatString' to an existing path" } <# .notes For use with WSUS Offline Updater #> function Get-WOShortCode { # TODO fixme I think I don't need this anymore because I'm not using WSUS Offline anymore param( [parameter(mandatory=$true)] [string] $OSName, [parameter(mandatory=$true)] [string] $OSArchitecture ) # I'm adding to this list slowly, only as I encounter the actual names from install.wim # on the trial CDs when I actually try to install them $shortCodeTable = @{ "8.1" = "w63" } $shortCodeTable.keys |% { if ($OSName -match $_) { $shortCode = $shortCodeTable[$_] } } if (-not $shortCode) { throw "Could not determine shortcode for an OS named '$OSName'" } if ($OSArchitecture -match $ArchitectureId.i386) { $shortCode += "" } elseif ($OSArchitecture -match $ArchitectureId.amd64) { $shortCode += "-x64" } else { throw "Could not determine shortcode for an OS of architecture '$OSArchitecture'" } write-verbose "Found shortcode '$shortcode' for OS named '$OSName' of architecture '$OSArchitecture'" return $shortCode } ### Publicly exported functions called directly from slipstreaming scripts <# .notes This is intended for use in the postinstall phase, on the target machine We expect calling scripts to get a lab temp dir with this function, but we do NOT permit functions in the module to call it TODO: does that even make sense to do?? Only functions that were intended to run in that phase should have the concept of a "LabTempDir". NOTE: this will return the same directory every time it's called until the module is reimported #> function Get-LabTempDir { if ("${script:WinTrialLabTemp}") {} # noop elseif ("${env:WinTrialLabTemp}") { $script:WinTrialLabTemp = $env:WinTrialLabTemp } else { $dateStamp = get-date -UFormat "%Y-%m-%d-%H-%M-%S" $script:WinTrialLabTemp = "${env:Temp}\WinTrialLab-$dateStamp" } $script:WinTrialLabTemp = [System.IO.Path]::GetFullPath($script:WinTrialLabTemp) write-verbose "Using WinTrialLabTemp directory at '${script:WinTrialLabTemp}'" if (-not (test-path $script:WinTrialLabTemp)) { write-verbose "Temporary directory does not exist, creating it..." mkdir -force $script:WinTrialLabTemp | out-null } return $script:WinTrialLabTemp } <# .description Return the OS Architecture of the current system, as determined by WMI Will return either "i386" or "amd64" TODO: this isn't a great method but I'm tired of trying to find the totally correct one. This one isn't ideal because OSArchitecture can be localized. I've seen some advice that you should call into the registry - reg Query "HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL && set OSARCHITECTURE=32BIT || set OSARCHITECTURE=64BIT - http://stackoverflow.com/a/24590583/868206 - https://support.microsoft.com/en-us/kb/556009 ... however, this lets you know about the HARDWARE, not the OPERATING SYSTEM - we care about the latter #> function Get-OSArchitecture { $OSArch = Get-WmiObject -class win32_operatingsystem -property osarchitecture | select -expand OSArchitecture if ($OSArch -match "64") { return $ArchitectureId.amd64 } elseif ($OSArch -match "32") { return $ArchitectureId.i386 } else { throw "Could not determine OS Architecture from string '$OSArch'" } } function Test-AdminPrivileges { [cmdletbinding()] param( [switch] $ThrowIfNotElevated ) $me = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() $elevated = $me.IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") if ($ThrowIfNotElevated -and (! $elevated)) { throw "Administrative privileges are required" } return $elevated } function Install-SevenZip { $OSArch = Get-OSArchitecture $szDlPath = Get-WebUrl -url $URLs.SevenZipDownload.$OSArch -outDir $env:temp try { write-verbose "Downloaded '$szUrl' to '$szDlPath', now running msiexec..." $msiCall = 'msiexec /qn /i "{0}"' -format $szDlPath # Windows suxxx so msiexec sometimes returns right away? or something idk. fuck Invoke-ExpressionAndCheck -command $msiCall -sleepSeconds 30 } finally { rm -force $szDlPath } } set-alias sevenzip "${env:ProgramFiles}\7-Zip\7z.exe" function Install-VBoxAdditions { [cmdletbinding()] param( [parameter(mandatory=$true)] [string] $isoPath ) $isoPath = resolve-path $isoPath | select -expand Path $vbgaPath = "${env:Temp}\InstallVbox" try { mkdir -force $vbgaPath write-verbose "Extracting iso at '$isoPath' to directory at '$vbgaPath'..." Invoke-ExpressionAndCheck -command ('sevenzip x "{0}" -o"{1}"' -f $isoPath, $vbgaPath) write-verbose "Installing the Oracle certificate..." $oracleCert = resolve-path "$vbgaPath\cert\oracle-vbox.cer" | select -expand path # NOTE: Checking for exit code, but this command will fail with an error if the cert is already installed Invoke-ExpressionAndCheck -command ('"{0}" add-trusted-publisher "{1}" --root "{1}"' -f "$vbgaPath\cert\VBoxCertUtil.exe",$oracleCert) write-verbose "Installing the virtualbox additions" Invoke-ExpressionAndCheck -command ('"{0}" /with_wddm /S' -f "$vbgaPath\VBoxWindowsAdditions.exe") # returns IMMEDIATELY, goddamn fuckers while (get-process -Name VBoxWindowsAdditions*) { write-verbose 'Waiting for VBox install to finish...'; sleep 1; } } finally { rm -recurse -force $vbgaPath } } function Disable-AutoAdminLogon { write-verbose "Function: $($MyInvocation.MyCommand)..." set-itemproperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name AutoAdminLogon -Value 0 } function Enable-RDP { # TODO fixme netsh advfirewall firewall add rule name="Open Port 3389" dir=in action=allow protocol=TCP localport=3389 reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f } function Install-CompiledDotNetAssemblies { # http://support.microsoft.com/kb/2570538 # http://robrelyea.wordpress.com/2007/07/13/may-be-helpful-ngen-exe-executequeueditems/ $ngen = "${env:WinDir}\microsoft.net\framework\v4.0.30319\ngen.exe" Invoke-ExpressionAndCheck -command "$ngen update /force /queue" Invoke-ExpressionAndCheck -command "$ngen executequeueditems" if ((Get-OSArchitecture) -match $ArchitectureId.amd64) { $ngen64 = "${env:WinDir}\microsoft.net\framework64\v4.0.30319\ngen.exe" Invoke-ExpressionAndCheck -command "$ngen64 update /force /queue" Invoke-ExpressionAndCheck -command "$ngen64 executequeueditems" } } function Compress-WindowsInstall { $OSArch = Get-OSArchitecture try { $udfZipPath = Get-WebUrl -url $URLs.UltraDefragDownload.$OSArch -outDir $env:temp $udfExPath = "${env:temp}\ultradefrag-portable-6.1.0.$udfArch" # This archive contains a folder - extract it directly to the temp dir Invoke-ExpressionAndCheck -command ('sevenzip x "{0}" "-o{1}"' -f $udfZipPath,$env:temp) $sdZipPath = Get-WebUrl -url $URLs.SdeleteDownload -outDir $env:temp $sdExPath = "${env:temp}\SDelete" # This archive does NOT contain a folder - extract it to a subfolder (will create if necessary) Invoke-ExpressionAndCheck -command ('sevenzip x "{0}" "-o{1}"' -f $sdZipPath,$sdExPath) stop-service wuauserv rm -recurse -force ${env:WinDir}\SoftwareDistribution\Download start-service wuauserv Invoke-ExpressionAndCheck -command ('{0} --optimize --repeat "{1}"' -f $udfExPath\udefrag.exe,"$env:SystemDrive") Set-ItemProperty -path HKCU\Software\Sysinternals\SDelete -name EulaAccepted -value 1 } Invoke-ExpressionAndCheck -command ('{0} -q -z "{1}"' -f $sdExPath,$env:SystemDrive) } finally { rm -recurse -force $udfZipPath,$udfExPath,$sdZipPath,$sdExPath -ErrorAction Continue } } function Disable-WindowsUpdates { Test-AdminPrivileges -ThrowIfNotElevated $Updates = (New-Object -ComObject "Microsoft.Update.AutoUpdate").Settings if ($Updates.ReadOnly) { throw "Cannot update Windows Update settings due to GPO restrictions." } $Updates.NotificationLevel = 1 # 1 = Disabled lol $Updates.Save() $Updates.Refresh() } function Enable-MicrosoftUpdate { [cmdletbinding()] param() write-verbose "Enabling Microsoft Update..." stop-service wuauserv $auKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" Set-ItemProperty -path $auKey -name EnableFeaturedSoftware -value 1 Set-ItemProperty -path $auKey -name IncludeRecommendedUpdates -value 1 $ServiceManager = New-Object -ComObject "Microsoft.Update.ServiceManager" $ServiceManager.AddService2("7971f918-a847-4430-9279-4a52d1efe18d",7,"") start-service wuauserv } function Install-Chocolatey { [cmdleetbinding()] param() $chocoExePath = "${env:ProgramData}\Chocolatey\bin" if ($($env:Path).ToLower().Contains($($chocoExePath).ToLower())) { write-verbose "Chocolatey already in path, exiting..." return } $systemPath = [Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::Machine) $systemPath += ";$chocoExePath" [Environment]::SetEnvironmentVariable("PATH", $systemPath, [System.EnvironmentVariableTarget]::Machine) $env:Path = $systemPath $userPath = [Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::User) if ($userPath) { $env:Path += ";$userPath" } iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1')) } function Set-UserOptions { [cmdletbinding()] param( [switch] $ShowHiddenFiles, [switch] $ShowSystemFiles, [switch] $ShowFileExtensions, [switch] $ShowStatusBar, [switch] $DisableSharingWizard, [switch] $EnablePSOnWinX, [switch] $EnableQuickEdit ) $explorerAdvancedKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' if ($ShowHiddenFiles) { Set-ItemProperty -path $explorerAdvancedKey -name Hidden -value 1 } if ($ShowSystemFiles) { Set-ItemProperty -path $explorerAdvancedKey -name ShowSuperHidden -value 1 } if ($ShowFileExtensions) { Set-ItemProperty -path $explorerAdvancedKey -name HideFileExt -value 0 } if ($ShowStatusBar) { Set-ItemProperty -path $explorerAdvancedKey -name ShowStatusBar -value 1 } if ($DisableSharingWizard) { Set-ItemProperty -path $explorerAdvancedKey -name SharingWizardOn -value 0 } if ($EnablePSOnWinX) { Set-ItemProperty -path $explorerAdvancedKey -name DontUsePowerShellOnWinX -value 0 } $consoleKey = "HKCU:\Console" if ($EnableQuickEdit) { Set-ItemProperty -path $consoleKey -name QuickEdit -value 1 } } function Disable-HibernationFile { [cmdletbinding()] param() write-verbose "Removing Hibernation file..." $powerKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Power' Set-ItemProperty -path $powerKey -name HibernateFileSizePercent -value 0 # hiberfil is zero bytes Set-ItemProperty -path $powerKey -name HibernateEnabled -value 0 # disable hibernation altogether } function Enable-WinRM { # TODO fixme, would prefer this to be in Powershell if possible. also it's totally insecure [cmdletbinding()] param() write-verbose "Enabling WinRM..." cmd /c winrm quickconfig -q cmd /c winrm quickconfig -transport:http cmd /c winrm set winrm/config @{MaxTimeoutms="1800000"} cmd /c winrm set winrm/config/winrs @{MaxMemoryPerShellMB="1800"} cmd /c winrm set winrm/config/service @{AllowUnencrypted="true"} cmd /c winrm set winrm/config/service/auth @{Basic="true"} cmd /c winrm set winrm/config/client/auth @{Basic="true"} cmd /c winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="5985"} cmd /c netsh advfirewall firewall set rule group="remote administration" new enable=yes cmd /c netsh firewall add portopening TCP 5985 "Port 5985" cmd /c net stop winrm cmd /c sc config winrm start= auto cmd /c net start winrm <# # cmd /c winrm quickconfig -q # cmd /c winrm quickconfig -transport:http Enable-PSRemoting –force -SkipNetworkProfileCheck set-item wsman:\localhost\MaxTimeoutms -value 1800000 -force set-item wsman:\localhost\Shell\MaxMemoryPerShellMB -value 1800 -force set-item wsman:\localhost\Service\AllowUnencrypted -value $true -force set-item wsman:\localhost\Service\Auth\Basic -value $true -force set-item wsman:\localhost\Client\Auth\Basic -value $true -force # cmd /c winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="5985"} $httpListener = $null foreach ($listener in (ls WSMan:\localhost\Listener)) { foreach ($key in $listener.keys) { $subKey = $key.split("=")[0] $subVal = $key.split("=")[1] if (($subKey -match "Transport") -and ($subVal = "HTTP")) { $httpListener = $listener break } } if ($httpListener) { break } } set-item $httpListener -value 5894 Set-Item wsman:\localhost\client\auth\CredSSP -value true -force Enable-WSManCredSSP -force -role server -force #set-item wsman:localhost\client\trustedhosts -value * -force #set-item wsman:localhost\client\trustedhosts -value 1.1.2.242 set-item wsman:\localhost\listener\listener*\port -value 81 -force stop-service winrm set-service -StartupType "automatic" start-service winrm winrm get winrm/config winrm enumerate winrm/config/listener #> } function Set-PasswordExpiry { # TODO fixme use pure Powershell [cmdletbinding()] param( [parameter(mandatory=$true)] [string] $accountName, [parameter(mandatory=$true)] [bool] $expirePassword ) $pe = "TRUE" if (-not $expirePassword) { $pe = "FALSE" } cmd.exe /c wmic useraccount where "name='$accountName'" set "PasswordExpires=$pe" } function New-WindowsInstallMedia { # TODO fixme not sure I wanna handle temp dirs this way?? [cmdletbinding()] param( [parameter(mandatory=$true)] [string] $sourceIsoPath, [parameter(mandatory=$true)] [string] $installMediaTemp, # WILL BE DELETED [parameter(mandatory=$true)] [string] $installWimPath, # your new install.wim file [parameter(mandatory=$true)] [string] $outputIsoPath ) $oscdImgPath = Get-AdkPath "{0}\Assessment and Deployment Kit\Deployment Tools\{1}\Oscdimg\oscdimg.exe" $installWimPath = resolve-path $installWimPath | select -expand path $installMediaTemp = mkdir -force $installMediaTemp | select -expand fullname $outputIsoParentPath = split-path $outputIsoPath -parent $outputIsoFilename = split-path $outputIsoPath -leaf $outputIsoParentPath = mkdir -force $outputIsoParentPath | select -expand fullname if (test-path $installMediaTemp) { rm -recurse -force $installMediaTemp } mkdir -force $installMediaTemp | out-null $diskVol = get-diskimage -imagepath $sourceIsoPath | get-volume if (-not $diskVol) { mount-diskimage -imagepath $sourceIsoPath $diskVol = get-diskimage -imagepath $sourceIsoPath | get-volume } $driveLetter = $diskVol | select -expand DriveLetter $existingInstallMediaDir = "${driveLetter}:" # TODO: the first copy here copies the original install.wim, and the second copies the new one over it # this is really fucking dumb right? but then, THIS is way fucking dumber: # http://stackoverflow.com/questions/731752/exclude-list-in-powershell-copy-item-does-not-appear-to-be-working # PS none of those solutions are generic enough to get included so fuck it copy-item -recurse -path "$existingInstallMediaDir\*" -destination "$installMediaTemp" -verbose:$verbose remove-item -force -path "$installMediaTemp\sources\install.wim" copy-item -path $installWimPath -destination "$installMediaTemp\sources\install.wim" -force -verbose:$verbose $etfsBoot = resolve-path "$existingInstallMediaDir\boot\etfsboot.com" | select -expand Path $oscdimgCall = '& "{0}" -m -n -b"{1}" "{2}" "{3}"' -f @($oscdImgPath, $etfsBoot, $installMediaTemp, $outputIsoPath) write-verbose "Calling OSCDIMG: '$oscdimgCall" Invoke-ExpressionAndCheck $oscdimgCall -verbose:$verbose dismount-diskimage -imagepath $sourceIsoPath } function Get-WindowsUpdateUrls { # TODO: is this how we wanna do temps tho? [cmdletbinding()] param( [parameter(mandatory=$true)] $windowsVersion, [parameter(mandatory=$true)] $osArchitecture, [parameter(mandatory=$true)] $packageXml, [parameter(mandatory=$true)] $outFile, [switch] $debugSaveXslt ) $xsltPath = [IO.Path]::GetTempFileName() write-verbose "Downloading XSLT to '$xsltPath'" if ($osArchitecture -match $ArchitectureId.i386) { $arch = "x86" } elseif ($osArchitecture -match $ArchitectureId.amd64) { $arch = "x64" } else { throw "Dunno bout architecture '$osArchitecture'" } $xsltUrl = "$WSUSOfflineRepoBaseUrl/xslt/ExtractDownloadLinks-$windowsVersion-$arch-glb.xsl" Get-WebFile -url $xsltUrl -outFile $xsltPath Apply-XmlTransform -xmlFile $packageXml -xsltFile $xsltPath -outFile $outFile if (-not $debugSaveXslt) { rm -force $xsltPath } return $outFile } # Exports: #TODO $emmParams = @{ Alias = @("sevenzip") Variable = @("ArchitectureId") Function = "*" # Function = @( # "Get-OSArchitecture" # "Get-LabTempDir" # "Install-SevenZip" # "Install-VBoxAdditions" # ) } export-modulemember @emmParams