Make postinstall scripts more robust

(Still having problems, but I'm knocking them out one by one)

- Enable UAC in win81x86
- Throw if my scripts don't pass syntax check BEFORE running Packer
- Make the postinstall scripts more robust in general
- Add more robust Invoke-ExpressionEx, remove Invoke-ExpressionAndLog
- Simplify win-updates.ps1. Don't duplicate code.
- Use scheduled tasks instead of registry keys for running postinstall
  commands after reboots
jowjDev
Micah R Ledbetter 9 years ago
parent 72059d3d80
commit 202851cb2d

@ -8,9 +8,7 @@ Which build actions do you want to perform?
.parameter tag .parameter tag
A tag for the temporary directory, the output directory, and the resulting Vagrant box A tag for the temporary directory, the output directory, and the resulting Vagrant box
#> #>
#function buildlab { [cmdletbinding()] param(
[cmdletbinding()]
param(
[parameter(mandatory=$true,ParameterSetName="BuildPacker")] [parameter(mandatory=$true,ParameterSetName="BuildPacker")]
[parameter(mandatory=$true,ParameterSetName="AddToVagrant")] [parameter(mandatory=$true,ParameterSetName="AddToVagrant")]
[parameter(mandatory=$true,ParameterSetName="VagrantUp")] [parameter(mandatory=$true,ParameterSetName="VagrantUp")]
@ -31,7 +29,8 @@ param(
) )
$errorActionPreference = "Stop" $errorActionPreference = "Stop"
Set-StrictMode -Version 2.0 Get-Module |? -Property Name -match "wintriallab-postinstall" | Remove-Module
import-module $PSScriptRoot\scripts\wintriallab-postinstall.psm1
$dateStamp = get-date -UFormat "%Y-%m-%d-%H-%M-%S" $dateStamp = get-date -UFormat "%Y-%m-%d-%H-%M-%S"
$packerOutDir = "$baseOutDir\PackerOut" $packerOutDir = "$baseOutDir\PackerOut"
@ -59,27 +58,6 @@ $packerConfigRoot = "${PSScriptRoot}\packer\${baseConfigName}"
$packerFile = "${packerConfigRoot}\${baseConfigName}.packerfile.json" $packerFile = "${packerConfigRoot}\${baseConfigName}.packerfile.json"
$packedBoxPath = "${outDir}\${baseConfigName}_virtualbox.box" $packedBoxPath = "${outDir}\${baseConfigName}_virtualbox.box"
$vagrantTemplate = "${packerConfigRoot}\vagrantfile-${baseConfigName}.template" $vagrantTemplate = "${packerConfigRoot}\vagrantfile-${baseConfigName}.template"
function Test-PowershellSyntax {
[cmdletbinding()]
param(
[parameter(mandatory=$true)] [string] $fileName,
[switch] $ThrowOnFailure
)
$tokens = @()
$parseErrors = @()
$parser = [System.Management.Automation.Language.Parser]
$fileName = resolve-path $fileName
$parsed = $parser::ParseFile($fileName, [ref]$tokens, [ref]$parseErrors)
if ($parseErrors.count -gt 0) {
$message = "$($parseErrors.count) parse errors found in file '$fileName':`r`n"
$parseErrors |% { $message += "`r`n $_" }
if ($ThrowOnFailure) { throw $message } else { write-verbose $message }
return $false
}
return $true
}
function Build-PackerFile { function Build-PackerFile {
[cmdletbinding()] [cmdletbinding()]
@ -185,7 +163,7 @@ function Show-LabVariable {
if (-not $SkipSyntaxcheck) { if (-not $SkipSyntaxcheck) {
foreach ($script in (gci $PSScriptRoot\scripts\* -include *.ps1,*.psm1)) { foreach ($script in (gci $PSScriptRoot\scripts\* -include *.ps1,*.psm1)) {
$valid = Test-PowershellSyntax -fileName $script.fullname $valid = Test-PowershellSyntax -fileName $script.fullname -ThrowOnFailure
New-Object PSObject -Property @{ New-Object PSObject -Property @{
ScriptName = $script.name ScriptName = $script.name
ValidSyntax = $valid ValidSyntax = $valid
@ -228,6 +206,3 @@ if ($AddToVagrant) {
if ($VagrantUp) { if ($VagrantUp) {
Run-VagrantBox -vagrantBoxName $fullConfigName -workingDirectory $outDir -whatif:$whatif Run-VagrantBox -vagrantBoxName $fullConfigName -workingDirectory $outDir -whatif:$whatif
} }
#}

@ -76,11 +76,6 @@
<UserLocale>en-US</UserLocale> <UserLocale>en-US</UserLocale>
</component> </component>
</settings> </settings>
<settings pass="offlineServicing">
<component name="Microsoft-Windows-LUA-Settings" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<EnableLUA>false</EnableLUA>
</component>
</settings>
<settings pass="oobeSystem"> <settings pass="oobeSystem">
<component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Shell-Setup" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <component xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Microsoft-Windows-Shell-Setup" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<UserAccounts> <UserAccounts>

@ -74,3 +74,8 @@ upstream improvements
- The shell, windows-shell, and powershell provisioners are VERY finicky. I canNOT make them work reliably. The easiest thing I can figure out how to do is to use a Powershell provisioner to call a file with no arguments over WinRM. lmfao - The shell, windows-shell, and powershell provisioners are VERY finicky. I canNOT make them work reliably. The easiest thing I can figure out how to do is to use a Powershell provisioner to call a file with no arguments over WinRM. lmfao
- However the situation was much improved when I switched to WinRM with the powershell provisioner. That seems to work OK - However the situation was much improved when I switched to WinRM with the powershell provisioner. That seems to work OK
- I think the problem was that using the shell provisioner with OpenSSH, which provides an emulated POSIX environment of some kind - I think the problem was that using the shell provisioner with OpenSSH, which provides an emulated POSIX environment of some kind
- There's lots of information on the Internet claiming you can use `HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices` (or `RunServicesOnce`) to run something at boot, before logon - analogous to the `Run`/`RunOnce` keys. This is apparently false for any NT operating system. Properties on these keys DO NOT RUN AT BOOT - they are completely ignored by the operating system.
- The original packer-windows crew got aroudn this by using the `Run` key and disabling UAC in `Autounattend.xml`
- I'm planning to get around this by creating a scheduled task that starts at boot and runs with highest privileges. This won't work pre-Vista/2008, but that's OK with me.
- This means I need to write an executor that can start at boot, and then check for things to execute located elsewhere. Bleh.

@ -1,4 +1,7 @@
[cmdletbinding()] param() [cmdletbinding(DefaultParameterSetName="RunWindowsUpdates")] param(
[Parameter(ParameterSetName="RunWindowsUpdates")] [switch] $RunWindowsUpdates,
[Parameter(Mandatory=$true,ParameterSetName="SkipWindowsUpdates")] [switch] $SkipWindowsUpdates
)
import-module $PSScriptRoot\wintriallab-postinstall.psm1 import-module $PSScriptRoot\wintriallab-postinstall.psm1
$errorActionPreference = "Stop" $errorActionPreference = "Stop"
@ -9,25 +12,22 @@ Invoke-ScriptblockAndCatch -scriptBlock {
Set-PasswordExpiry -accountName "vagrant" -expirePassword $false Set-PasswordExpiry -accountName "vagrant" -expirePassword $false
Disable-HibernationFile Disable-HibernationFile
Enable-MicrosoftUpdate Enable-MicrosoftUpdate
Set-AllNetworksToPrivate # Required for Windows 10, not required for 81, not sure about other OSes
Install-VBoxAdditions -fromDisc # Need to reboot for some of these drivers to take
Set-PinnedApplication -Action PinToTaskbar -Filepath "$PSHOME\Powershell.exe" # Need to reboot for some of these drivers to take
Set-PinnedApplication -Action PinToTaskbar -Filepath "${env:SystemRoot}\system32\eventvwr.msc" # Requires that the packer file attach the Guest VM driver disc, rather than upload it
# (Uploading it also gives problems when using WinRM - too big? - so this is a better solution anyway)
Install-VBoxAdditions -fromDisc
# To reboot, then run Windows updates, then enable WinRM: # Required for Windows 10, not required for 81, not sure about other OSes
$winRmCommand = "$PSHome\powershell.exe -File A:\enable-winrm.ps1" # Should probably happen after installing Guest VM drivers, in case installing the drivers would cause Windows to see the network as a new connection
$winUpdateCommand = "$PSHOME\powershell.exe -File A:\win-updates.ps1 -RestartAction RunAtLogon -PostUpdateExpression `"$winRmCommand`"" Set-AllNetworksToPrivate
# To install Windows Updates then enable WinRM after reboot: switch ($PsCmdlet.ParameterSetName) {
Set-RestartRegistryEntry -restartAction RunAtLogon -restartCommand $winUpdateCommand "RunWindowsUpdates" { $restartCommand = [ScriptBlock]::Create("A:\win-updates.ps1 -PostUpdateExpression A:\enable-winrm.ps1") }
"SkipWindowsUpdates" { $restartCommand = [ScriptBlock]::Create("A:\enable-winrm.ps1") }
# To just enable WinRM without installing updates after reboot: default { throw "Not configured for this parameter set..." }
#Set-RestartRegistryEntry -restartAction RunAtLogon -restartCommand $winRmCommand }
Set-RestartScheduledTask -RestartCommand $restartCommand | out-null
$message = "Checking restart registry key: `r`n"
Get-RestartRegistryEntry | select -expand StringRepr |% { $message += "`r`n$_`r`n"}
Write-EventLogWrapper $message
Restart-Computer -force Restart-Computer -force
} }

@ -6,13 +6,19 @@ Don't require parameters - it won't run with parameters during post install. Thi
$packerBuildName = ${env:PACKER_BUILD_NAME}, $packerBuildName = ${env:PACKER_BUILD_NAME},
$packerBuilderType = ${env:PACKER_BUILDER_TYPE} $packerBuilderType = ${env:PACKER_BUILDER_TYPE}
) )
$errorActionPreference = "stop" $errorActionPreference = "Continue"
import-module $PSScriptRoot\wintriallab-postinstall.psm1 import-module $PSScriptRoot\wintriallab-postinstall.psm1
# Gotta do this outside Invoke-ScriptblockAndCatch because I need to use try/catch here:
# These commands are fragile and shouldn't fail the build if they fail, so I put them in a try/catch outside of Invoke-ScriptblockAndCatch
try { try {
Set-PinnedApplication -Action UnpinFromTaskbar -Filepath "C:\Program Files\WindowsApps\Microsoft.WindowsStore_2015.10.5.0_x86__8wekyb3d8bbwe\WinStore.Mobile.exe" -ErrorAction Continue Set-PinnedApplication -Action UnpinFromTaskbar -Filepath "C:\Program Files\WindowsApps\Microsoft.WindowsStore_2015.10.5.0_x86__8wekyb3d8bbwe\WinStore.Mobile.exe" -ErrorAction Continue
Set-PinnedApplication -Action PinToTaskbar -Filepath "$PSHOME\Powershell.exe"
Set-PinnedApplication -Action PinToTaskbar -Filepath "${env:SystemRoot}\system32\eventvwr.msc"
$UserPinnedTaskBar = "${env:AppData}\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar"
if (test-path "$UserPinnedTaskBar\Server Manager.lnk") { rm "$UserPinnedTaskBar\Server Manager.lnk" }
} }
catch {} catch {}
Invoke-ScriptblockAndCatch -scriptBlock { Invoke-ScriptblockAndCatch -scriptBlock {
Write-EventLogWrapper "PostInstall for packer build '$packerBuildName' of type '$packerBuilderType'" Write-EventLogWrapper "PostInstall for packer build '$packerBuildName' of type '$packerBuilderType'"
Install-SevenZip Install-SevenZip
@ -33,14 +39,6 @@ Invoke-ScriptblockAndCatch -scriptBlock {
} }
Set-UserOptions @suoParams Set-UserOptions @suoParams
# TODO: This would be better done as links because they're easier to deal with later
# TODO: document the difference between using Set-PinnedApplication vs the links in AppData
Set-PinnedApplication -Action PinToTaskbar -Filepath "$PSHOME\Powershell.exe"
Set-PinnedApplication -Action PinToTaskbar -Filepath "${env:SystemRoot}\system32\eventvwr.msc"
$UserPinnedTaskBar = "${env:AppData}\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar"
if (test-path "$UserPinnedTaskBar\Server Manager.lnk") { rm "$UserPinnedTaskBar\Server Manager.lnk" }
Install-CompiledDotNetAssemblies # Takes about 15 minutes for me Install-CompiledDotNetAssemblies # Takes about 15 minutes for me
Compress-WindowsInstall # Takes maybe another 15 minutes Compress-WindowsInstall # Takes maybe another 15 minutes
} }

@ -4,23 +4,18 @@ Run Windows Update, installing all available updates and rebooting as necessary
.parameter MaxCycles .parameter MaxCycles
The number of times to check for updates before forcing a reboot, even if Windows Update has not indicated that one is required The number of times to check for updates before forcing a reboot, even if Windows Update has not indicated that one is required
TODO: this doesn't appear to be working reliably TODO: this doesn't appear to be working reliably
.parameter ScriptProductName
The name for this script that you want to make visible to the sysadmin in logs.
.parameter PostUpdateExpression .parameter PostUpdateExpression
A string representing a PowerShell expression that is run one time after all updates have been applied (or MaxCycles has been reached without rebooting) A string representing a PowerShell expression that is run one time after all updates have been applied (or MaxCycles has been reached without rebooting)
TODO: the parenthetical is nonobvious behavior and should be eliminated. In fact the whole of MaxCycles should be rethought. TODO: the parenthetical is nonobvious behavior and should be eliminated. In fact the whole of MaxCycles should be rethought.
.parameter RestartAction .parameter NoRestart
When reboots are required, you can choose to run this script again before logon (using the RunServicesOnce key) or after logon (using the RunOnce key), or to skip reboots. Don't reboot (useful for debugging)
.notes .notes
This script can be run directly, but it can also be dot-sourced to get access to internal functions without automatically checking for updates, applying them, or rebooting This script can be run directly, but it can also be dot-sourced to get access to internal functions without automatically checking for updates, applying them, or rebooting
This script is intended to be 100% standalone because it needs to be able to tell Windows to call it again upon reboot. There are intentionally no dependencies, and features related to Windows Update that don't fit here (such as enable Microsoft Update) should live elsewhere
#> #>
param( param(
[int] $MaxCycles = 5, [int] $MaxCycles = 5,
[string] $ScriptProductName = "WinUp-Marionettist",
[string] $PostUpdateExpression, [string] $PostUpdateExpression,
[string] [ValidateSet('RunBeforeLogon','RunAtLogon','NoRestart')] $RestartAction = "NoRestart", [switch] $NoRestart
[switch] $CalledFromRegistry
) )
<# Notes on the code: <# Notes on the code:
@ -28,123 +23,39 @@ param(
I'm trying to use StrictMode. That ends up making some code more complex than it would have to be otherwise. For instance, sometimes I have to check that a property exists (like $SomeVar.PSObject.Properties['PropertyName']) before I can use it. I'm trying to use StrictMode. That ends up making some code more complex than it would have to be otherwise. For instance, sometimes I have to check that a property exists (like $SomeVar.PSObject.Properties['PropertyName']) before I can use it.
TODO: Make sure every path of my program is writing unique log messages. Make sure all those paths are working. TODO: Make sure every path of my program is writing unique log messages. Make sure all those paths are working.
TODO: Pass unique event IDs for every event to Write-WinUpEventLog. ?? TODO: Pass unique event IDs for every event to Write-EventLogWrapper. ??
#> #>
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
Set-StrictMode -version 2.0
import-module $PSScriptRoot\wintriallab-postinstall.psm1
# I need to get the path to this script from inside functions, which mess with the $MyInvocation variable # I need to get the path to this script from inside functions, which mess with the $MyInvocation variable
$script:ScriptPath = $MyInvocation.MyCommand.Path $script:ScriptPath = $MyInvocation.MyCommand.Path
$script:RestartRegistryKeys = @{
RunBeforeLogon = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunServicesOnce"
RunAtLogon = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
}
$script:RestartRegistryProperty = "$ScriptProductName"
<#
.synopsis
Write to a special event log, named after the $ScriptProductName.
If that event log doesn't already exist, create it first.
#>
function Write-WinUpEventLog {
param(
[parameter(mandatory=$true)] [String] $message,
[int] $eventId = 0,
[ValidateSet("Error",'Warning','Information','SuccessAudit','FailureAudit')] $entryType = "Information"
)
$eventLogName = $ScriptProductName
if (-not (get-eventlog -logname * |? { $_.Log -eq $eventLogName })) {
New-EventLog -Source $ScriptProductName -LogName $eventLogName
}
$messagePlus = "$message`r`n`r`nScript: $($script:ScriptPath)`r`nUser: ${env:USERDOMAIN}\${env:USERNAME}"
Write-Host -foreground magenta "====Writing to $ScriptProductName event log===="
write-host -foreground darkgray "$messagePlus`r`n"
Write-EventLog -LogName $eventLogName -Source $ScriptProductName -EventID $eventId -EntryType $entryType -Message $MessagePlus
}
<# function Restart-ComputerAndUpdater {
.synopsis
Create and set the registry property which will run this script on reboot
#>
function Set-RestartRegistryEntry {
param( param(
[parameter(mandatory=$true)] [int] $CyclesRemaining, [parameter(mandatory=$true)] [int] $CyclesRemaining,
[parameter(mandatory=$true)] [ValidateSet('RunBeforeLogon','RunAtLogon','NoRestart')] [string] $RestartAction, [parameter(mandatory=$true)] [switch] $NoRestart
[string] $scriptPath = $script:ScriptPath
) )
$restartCommandComponents = @(
if ($RestartAction -match "NoRestart") { ('& "{0}"' -f $scriptPath)
Write-WinUpEventLog "Called Set-RestartRegistryEntry with -RestartAction NoRestart, will not write registry key"
return
}
$psCallComponents = @(
"$PSHOME\powershell.exe"
('-File "{0}"' -f $scriptPath)
"-MaxCycles $CyclesRemaining" "-MaxCycles $CyclesRemaining"
"-ScriptProductName $ScriptProductName"
"-CalledFromRegistry"
"-RestartAction ${script:RestartAction}"
('-PostUpdateExpression "{0}"' -f "$PostUpdateExpression")
)
$psCall = $psCallComponents -join " "
$message = "Setting the Restart Registry Key at: {0}\{1}`r`n{2}" -f $script:RestartRegistryKeys.$RestartAction, $script:RestartRegistryProperty, $psCall
Write-WinUpEventLog -message $message
New-Item $script:RestartRegistryKeys.$RestartAction -force | out-null
Set-ItemProperty -Path $script:RestartRegistryKeys.$RestartAction -Name $script:RestartRegistryProperty -Value $psCall
}
function Remove-RestartRegistryEntries {
[cmdletbinding()] param()
foreach ($key in $script:RestartRegistryKeys.Keys) {
try { Remove-ItemProperty -Path $script:RestartRegistryKeys[$key] -name $script:RestartRegistryProperty} catch {}
}
}
function Get-RestartRegistryEntry {
[cmdletbinding()] param()
$entries = @()
foreach ($key in $script:RestartRegistryKeys.Keys) {
$regKey = $script:RestartRegistryKeys[$key]
$entry = New-Object PSObject -Property @{
RegistryKey = $regKey
RegistryProperty = $script:RestartRegistryProperty
PropertyValue = $null
}
if (test-path $regKey) {
$regProps = get-item $regKey | select -expand Property
if ($regProps -contains $script:RestartRegistryProperty) {
$entry.PropertyValue = Get-ItemProperty -path $regKey | select -expand $script:RestartRegistryProperty
}
}
Add-Member -inputObject $entry -memberType ScriptProperty -Name StringRepr -Value {
"Key: $($this.RegistryKey), Property: $($this.RegistryProperty), Value: $($this.PropertyValue)"
}
$entries += @($entry)
}
return $entries
}
function Restart-ComputerAndUpdater {
param(
[parameter(mandatory=$true)] [int] $CyclesRemaining,
[parameter(mandatory=$true)] [ValidateSet('RunBeforeLogon','RunAtLogon','NoRestart')] [string] $RestartAction
) )
Remove-RestartRegistryEntries if ($PostUpdateExpression) { $restartCommandComponents += '-PostUpdateExpression "{0}"' -f "$PostUpdateExpression" }
if ($RestartAction -match "NoRestart") { if ($NoRestart) { $restartCommandComponents += "-NoRestart" }
Write-WinUpEventLog "Restart-ComputerAndUpdater was called, but '-RestartAction NoRestart' was passed; exiting instead..." $restartCommand = [ScriptBlock]::Create(($restartCommandComponents -join " "))
Set-RestartScheduledTask -RestartCommand $restartCommand | out-null
if ($NoRestart) {
Write-EventLogWrapper "Restart-ComputerAndUpdater was called, but '-NoRestart' was passed; exiting instead."
exit 1 exit 1
} }
else { else {
Set-RestartRegistryEntry -CyclesRemaining $CyclesRemaining -RestartAction $RestartAction Write-EventLogWrapper "Restarting..."
$message = "Rebooting...`r`n`r`nChecking restart registry key:"
Get-RestartRegistryEntry | select -expand StringRepr |% { $message += "`r`n`r`n$_"}
Write-WinUpEventLog $message
Restart-Computer -Force Restart-Computer -Force
exit 0 # Restarting returns immediately and script execution continues until the reboot is processed. Lol. exit 0 # Restarting returns immediately and script execution continues until the reboot is processed. Lol.
} }
} }
<# <#
@ -154,7 +65,7 @@ Return a new Microsoft Update Session for use with Check-WindowsUpdates and Inst
function New-UpdateSession { function New-UpdateSession {
[cmdletbinding()] param() [cmdletbinding()] param()
$UpdateSession = New-Object -ComObject 'Microsoft.Update.Session' $UpdateSession = New-Object -ComObject 'Microsoft.Update.Session'
$UpdateSession.ClientApplicationID = "$ScriptProductName" $UpdateSession.ClientApplicationID = "win-updates.ps1"
return $UpdateSession return $UpdateSession
} }
@ -164,11 +75,11 @@ Run the expression passed to this script with -PostUpdateExpression, if any
#> #>
function Run-PostUpdate { function Run-PostUpdate {
if ($PostUpdateExpression) { if ($PostUpdateExpression) {
Write-WinUpEventLog -message "Running PostUpdate expression:`r`n`r`n${PostUpdateExpression}" Write-EventLogWrapper -message "Running PostUpdate expression:`r`n`r`n${PostUpdateExpression}"
Invoke-Expression $PostUpdateExpression Invoke-Expression $PostUpdateExpression
} }
else { else {
Write-WinUpEventLog -message "No PostUpdate to run" Write-EventLogWrapper -message "No PostUpdate to run"
} }
} }
@ -185,17 +96,17 @@ function Get-RestartPendingStatus {
# Component Based Servicing (aka Windows components) (Vista+ only) # Component Based Servicing (aka Windows components) (Vista+ only)
$CsbProp = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing" $CsbProp = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing"
if ($CsbProp.PSObject.Properties['RebootPending']) { if ($CsbProp.PSObject.Properties['RebootPending']) {
Write-WinUpEventLog "(Un)installation of a Windows component requires a restart" Write-EventLogWrapper "(Un)installation of a Windows component requires a restart"
return $true return $true
} }
$WuauProp = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" $WuauProp = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update"
if ($WuauProp.PSObject.Properties['RebootRequired']) { if ($WuauProp.PSObject.Properties['RebootRequired']) {
Write-WinUpEventLog "Updates have been installed recently, but the machine must be restarted before installation is complete" Write-EventLogWrapper "Updates have been installed recently, but the machine must be restarted before installation is complete"
return $true return $true
} }
Write-WinUpEventLog "There are no Windows Update -related reboots pending" Write-EventLogWrapper "There are no Windows Update -related reboots pending"
return $false return $false
} }
@ -219,7 +130,7 @@ function Install-WindowsUpdates {
) )
if (-not $UpdateList) { if (-not $UpdateList) {
Write-WinUpEventLog -message "No Updates To Download..." Write-EventLogWrapper -message "No Updates To Download..."
return $false return $false
} }
@ -239,9 +150,9 @@ function Install-WindowsUpdates {
} }
$message = "There were $($AcceptedEulas.count) updates with a EULA which was automatically accepted`r`n" $message = "There were $($AcceptedEulas.count) updates with a EULA which was automatically accepted`r`n"
$AcceptedEulas |% { $message += "`r`n - $($_.Title)"} $AcceptedEulas |% { $message += "`r`n - $($_.Title)"}
Write-WinUpEventLog $message Write-EventLogWrapper $message
Write-WinUpEventLog -message 'Downloading Updates...' Write-EventLogWrapper -message 'Downloading Updates...'
$ok = $false $ok = $false
$attempts = 0 $attempts = 0
while ((! $ok) -and ($attempts -lt $MaxDownloadAttempts)) { while ((! $ok) -and ($attempts -lt $MaxDownloadAttempts)) {
@ -253,7 +164,7 @@ function Install-WindowsUpdates {
} }
catch { catch {
$message = "Error downloading updates. Retrying in 30s.`r`n`r`n$($_.Exception)" $message = "Error downloading updates. Retrying in 30s.`r`n`r`n$($_.Exception)"
Write-WinUpEventLog -message $message Write-EventLogWrapper -message $message
$attempts += 1 $attempts += 1
Start-Sleep -s 30 Start-Sleep -s 30
} }
@ -267,7 +178,7 @@ function Install-WindowsUpdates {
$UpdatesToInstall.Add($Update) |Out-Null $UpdatesToInstall.Add($Update) |Out-Null
} }
} }
Write-WinUpEventLog -message $message Write-EventLogWrapper -message $message
$RebootRequired = $false $RebootRequired = $false
if ($UpdatesToInstall.Count -gt 0) { if ($UpdatesToInstall.Count -gt 0) {
@ -275,7 +186,7 @@ function Install-WindowsUpdates {
$Installer.Updates = $UpdatesToInstall $Installer.Updates = $UpdatesToInstall
if ($Installer.PSObject.Properties['RebootRequiredBeforeInstallation'] -and $Installer.RebootRequiredBeforeInstallation) { if ($Installer.PSObject.Properties['RebootRequiredBeforeInstallation'] -and $Installer.RebootRequiredBeforeInstallation) {
Write-WinUpEventLog "Reboot required before installation. (This can happen when updates are installed but the machine is not rebooted before trying to install again.)" Write-EventLogWrapper "Reboot required before installation. (This can happen when updates are installed but the machine is not rebooted before trying to install again.)"
return $true return $true
} }
@ -288,10 +199,10 @@ function Install-WindowsUpdates {
$message += "`r`n`r`nItem: $($update.Title)" $message += "`r`n`r`nItem: $($update.Title)"
$message += "`r`nResult: $ResultCode" $message += "`r`nResult: $ResultCode"
} }
Write-WinUpEventLog -message $message Write-EventLogWrapper -message $message
} }
else { else {
Write-WinUpEventLog -message 'No updates available to install...' Write-EventLogWrapper -message 'No updates available to install...'
} }
return $RebootRequired return $RebootRequired
} }
@ -314,7 +225,7 @@ function Check-WindowsUpdates {
[switch] $FilterInteractiveUpdates, [switch] $FilterInteractiveUpdates,
[int] $MaxSearchAttempts = 12 [int] $MaxSearchAttempts = 12
) )
Write-WinUpEventLog -message "Checking for Windows Updates at $(Get-Date)" -eventId 104 Write-EventLogWrapper -message "Checking for Windows Updates at $(Get-Date)" -eventId 104
$SearchResult = $null $SearchResult = $null
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher() $UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
@ -327,7 +238,7 @@ function Check-WindowsUpdates {
} }
catch { catch {
$message = "Search call to UpdateSearcher was unsuccessful. Retrying in 10s.`r`n`r`n$($_.Exception | Format-List -force)" $message = "Search call to UpdateSearcher was unsuccessful. Retrying in 10s.`r`n`r`n$($_.Exception | Format-List -force)"
Write-WinUpEventLog -message $message Write-EventLogWrapper -message $message
$attempts += 1 $attempts += 1
Start-Sleep -s 10 Start-Sleep -s 10
} }
@ -358,7 +269,7 @@ function Check-WindowsUpdates {
if ($SkippedUpdates.Count -gt 0) { if ($SkippedUpdates.Count -gt 0) {
$SkippedUpdates |% { $message += "`r`n - $($_.Title)" } $SkippedUpdates |% { $message += "`r`n - $($_.Title)" }
} }
Write-WinUpEventLog -message $message Write-EventLogWrapper -message $message
return $ApplicableUpdates return $ApplicableUpdates
} }
@ -366,32 +277,34 @@ function Check-WindowsUpdates {
<# <#
.synopsis .synopsis
Run Windows Update, installing all available updates and rebooting as necessary Run Windows Update, installing all available updates and rebooting as necessary
.notes
- Skips any update that requires user interaction
- Automatically accepts the EULA of all the updates it installs
#> #>
function Run-WindowsUpdate { function Run-WindowsUpdate {
[cmdletbinding()] param() [cmdletbinding()] param()
$message = "Running Windows Update " $message = "Running Windows Update..."
$message += if ($CalledFromRegistry) { "from the Restart Registry Entry" } else { "after being called directly" } Write-EventLogWrapper $message
Write-WinUpEventLog $message
if (Get-RestartPendingStatus) { if (Get-RestartPendingStatus) {
Restart-ComputerAndUpdater -CyclesRemaining $maxCycles -RestartAction $script:RestartAction Restart-ComputerAndUpdater -CyclesRemaining $maxCycles -NoRestart:$NoRestart
} }
$UpdateSession = New-UpdateSession $UpdateSession = New-UpdateSession
for (; $maxCycles -gt 0; $maxCycles -= 1) { for (; $maxCycles -gt 0; $maxCycles -= 1) {
Write-WinUpEventLog -message "Starting to check for updates. $maxCycles cycles remain." Write-EventLogWrapper -message "Starting to check for updates. $maxCycles cycles remain."
$CheckedUpdates = Check-WindowsUpdates -FilterInteractiveUpdates -UpdateSession $UpdateSession $CheckedUpdates = Check-WindowsUpdates -FilterInteractiveUpdates -UpdateSession $UpdateSession
if (-not $CheckedUpdates) { if (-not $CheckedUpdates) {
Write-WinUpEventLog "No applicable updates were detected. Done!" Write-EventLogWrapper "No applicable updates were detected. Done!"
break break
} }
$RebootRequired = Install-WindowsUpdates -UpdateSession $UpdateSession -UpdateList $CheckedUpdates $RebootRequired = Install-WindowsUpdates -UpdateSession $UpdateSession -UpdateList $CheckedUpdates
if ($RebootRequired) { if ($RebootRequired) {
Write-WinUpEventLog -message "Restart Required - Restarting..." Write-EventLogWrapper -message "Restart Required - Restarting..."
Restart-ComputerAndUpdater -CalledFromRegistry -CyclesRemaining $maxCycles -RestartAction $script:RestartAction Restart-ComputerAndUpdater -CyclesRemaining $maxCycles -NoRestart:$NoRestart
} }
} }
Run-PostUpdate Run-PostUpdate
@ -406,7 +319,7 @@ if ($MyInvocation.InvocationName -ne '.') {
$message += "======== ERROR STACK ========`r`n" $message += "======== ERROR STACK ========`r`n"
$error |% { $message += "$_`r`n----`r`n" } $error |% { $message += "$_`r`n----`r`n" }
$message += "======== ========" $message += "======== ========"
Write-WinUpEventLog $message Write-EventLogWrapper $message
exit 666 exit 666
} }
} }

@ -29,11 +29,6 @@ $URLs = @{
SdeleteDownload = "http://download.sysinternals.com/files/SDelete.zip" SdeleteDownload = "http://download.sysinternals.com/files/SDelete.zip"
} }
$script:ScriptPath = $MyInvocation.MyCommand.Path $script:ScriptPath = $MyInvocation.MyCommand.Path
$script:RestartRegistryKeys = @{
RunBeforeLogon = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunServicesOnce"
RunAtLogon = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
}
$script:RestartRegistryProperty = "$ScriptProductName"
### Private support functions I use behind the scenes ### Private support functions I use behind the scenes
@ -54,25 +49,38 @@ function Get-WebUrl {
<# <#
.synopsis .synopsis
Invoke an expression; log the expression, any output, and the last exit code Invoke an expression; log the expression, optionally with any output, and the last exit code if appropriate
#> #>
function Invoke-ExpressionAndLog { function Invoke-ExpressionEx {
[cmdletbinding()] param( [cmdletbinding()] param(
[parameter(mandatory=$true)] [string] $command, [parameter(mandatory=$true)] [string] $command,
[switch] $invokeWithCmdExe, [switch] $invokeWithCmdExe,
[switch] $checkExitCode, [switch] $checkExitCode,
[switch] $logToStdout,
[int] $sleepSeconds [int] $sleepSeconds
) )
$global:LASTEXITCODE = 0 $global:LASTEXITCODE = 0
if ($invokeWithCmdExe) { $commandSb = if ($invokeWithCmdExe) {{cmd /c "$command"}} else {{invoke-expression -command $command}}
Write-EventLogWrapper "Invoking CMD: '$command'" Write-EventLogWrapper "Invoke-ExpressionEx called to run command '$command'`r`n`r`nUsing scriptblock: $($commandSb.ToString())"
$output = cmd /c "$command" $output = $null
try {
if ($logToStdout) {
$commandSb.invoke()
$message = "Expression '$command' exited with code '$LASTEXITCODE'"
}
else {
$output = $commandSb.invoke()
$message = "Expression '$command' exited with code '$LASTEXITCODE' and output the following to the console:`r`n`r`n$output"
}
Write-EventLogWrapper -message $message
} }
else { catch {
Write-EventLogWrapper "Invoking Powershell expression: '$command'" Write-EventLogWrapper -message "Invoke-ExpressionEx faile3d to run command '$command'"
$output = invoke-expression -command $command Write-ErrorStackToEventLog -errorStack $_
throw $_
} }
Write-EventLogWrapper "Expression '$command' had a last exit code of '$LastExitCode' and output the following to the console:`r`n`r`n$output"
if ($checkExitCode -and $global:LASTEXITCODE -ne 0) { if ($checkExitCode -and $global:LASTEXITCODE -ne 0) {
throw "LASTEXITCODE: ${global:LASTEXITCODE} for command: '${command}'" throw "LASTEXITCODE: ${global:LASTEXITCODE} for command: '${command}'"
} }
@ -97,6 +105,7 @@ function Write-EventLogWrapper {
New-EventLog -Source $EventLogSource -LogName $eventLogName New-EventLog -Source $EventLogSource -LogName $eventLogName
} }
$messagePlus = "$message`r`n`r`nScript: $($script:ScriptPath)`r`nUser: ${env:USERDOMAIN}\${env:USERNAME}" $messagePlus = "$message`r`n`r`nScript: $($script:ScriptPath)`r`nUser: ${env:USERDOMAIN}\${env:USERNAME}"
if ($messagePlus.length -gt 32766) { $messagePlus = $messagePlus.SubString(0,32766) } # Because Write-EventLog will die otherwise
Write-Host -foreground magenta "====Writing to $EvengLogName event log====" Write-Host -foreground magenta "====Writing to $EvengLogName event log===="
write-host -foreground darkgray "$messagePlus`r`n" write-host -foreground darkgray "$messagePlus`r`n"
Write-EventLog -LogName $eventLogName -Source $EventLogSource -EventID $eventId -EntryType $entryType -Message $MessagePlus Write-EventLog -LogName $eventLogName -Source $EventLogSource -EventID $eventId -EntryType $entryType -Message $MessagePlus
@ -134,55 +143,89 @@ function Write-ErrorStackToEventLog {
Write-EventLogWrapper $message Write-EventLogWrapper $message
} }
<# function Test-PowershellSyntax {
.synopsis [cmdletbinding(DefaultParameterSetName='FromText')]
Create and set the registry property which will run this script on reboot
#>
function Set-RestartRegistryEntry {
param( param(
[parameter(mandatory=$true)] [ValidateSet('RunBeforeLogon','RunAtLogon','NoRestart')] [string] $RestartAction, [parameter(mandatory=$true,ParameterSetName='FromText')] [string] $text,
[string] $restartCommand [parameter(mandatory=$true,ParameterSetName='FromFile')] [string] $fileName,
[switch] $ThrowOnFailure
) )
$tokens = @()
$parseErrors = @()
$parser = [System.Management.Automation.Language.Parser]
if ($pscmdlet.ParameterSetName -eq 'FromText') {
$parsed = $parser::ParseInput($text, [ref]$tokens, [ref]$parseErrors)
}
elseif ($pscmdlet.ParameterSetName -eq 'FromFile') {
$fileName = resolve-path $fileName
$parsed = $parser::ParseFile($fileName, [ref]$tokens, [ref]$parseErrors)
}
write-verbose "$($tokens.count) tokens found."
if ($RestartAction -match "NoRestart") { if ($parseErrors.count -gt 0) {
Write-EventLogWrapper "Called Set-RestartRegistryEntry with -RestartAction NoRestart, will not write registry key" $message = "$($parseErrors.count) parse errors found in file '$fileName':`r`n"
return $parseErrors |% { $message += "`r`n $_" }
if ($ThrowOnFailure) { throw $message } else { write-verbose $message }
return $false
} }
return $true
}
<#
.description
Set a scheduled task to run on next logon of the calling user. Intended for tasks that need to reboot and then be restarted such as applying Windows Updates
.notes
The Powershell New-ScheduledTask cmdlet is broken for me on Win81, but SchTasks.exe doesn't support actions with long arguments (requires a command line of < 200something characters). lmfao.
My workaround is to take a scriptblock, and then just save it to a file and call the file from Powershell.
I create the scheduled task with SchTasks.exe, then modify it with Powershell cmdlets that can handle long arguments just fine
#>
function Set-RestartScheduledTask {
[cmdletbinding()] param(
[Parameter(Mandatory=$true)] [Scriptblock] $restartCommand,
[string] $tempRestartScriptPath = "${env:temp}\$ScriptProductName-TempRestartScript.ps1",
[string] $taskName = "$ScriptProductName-RestartTask"
)
Remove-RestartScheduledTask -taskName $taskName
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent().Name
$restartCommand.ToString() | Out-File -FilePath $tempRestartScriptPath
"Unregister-ScheduledTask -taskName '$taskName' -Confirm:`$false" | Out-File -Append -FilePath $tempRestartScriptPath
Test-PowershellSyntax -ThrowOnFailure -FileName $tempRestartScriptPath
$schTasksCmd = 'SchTasks.exe /create /sc ONLOGON /tn "{0}" /tr "cmd.exe /c echo TemporparyPlaceholderCommand" /ru "{1}" /it /rl HIGHEST /f' -f $taskName,$currentUser
Invoke-ExpressionEx -command $schTasksCmd -invokeWithCmdExe -checkExitCode
# SchTasks.exe cannot specify a user for the LOGON schedule - it applies to all users. Modify it here:
$trigger = New-ScheduledTaskTrigger -AtLogon -User $currentUser
# SchTasks.exe cannot specify an action with long arguments (maxes out at like 200something chars). Modify it here:
$action = New-ScheduledTaskAction -Execute "$PSHome\Powershell.exe" -Argument "-File `"$tempRestartScriptPath`""
Set-ScheduledTask -taskname $taskName -action $action -trigger $trigger
$message = "Setting the Restart Registry Key at: {0}\{1}`r`n{2}" -f $script:RestartRegistryKeys.$RestartAction, $script:RestartRegistryProperty, $restartCommand $message = "Created scheduled task called '$taskName', which will run a temp file at '$tempRestartScriptPath', containing:`r`n`r`n"
Write-EventLogWrapper -message $message $message += (Get-Content $tempRestartScriptPath) -join "`r`n"
New-Item $script:RestartRegistryKeys.$RestartAction -force | out-null Write-EventLogWrapper -message $message
Set-ItemProperty -Path $script:RestartRegistryKeys.$RestartAction -Name $script:RestartRegistryProperty -Value $restartCommand
} }
function Get-RestartRegistryEntry { function Get-RestartScheduledTask {
[cmdletbinding()] param() [cmdletbinding()] param(
$entries = @() [string] $taskName = $ScriptProductName
foreach ($key in $script:RestartRegistryKeys.Keys) { )
$regKey = $script:RestartRegistryKeys[$key] Get-ScheduledTask |? -Property TaskName -match $taskName
$entry = New-Object PSObject -Property @{
RegistryKey = $regKey
RegistryProperty = $script:RestartRegistryProperty
PropertyValue = $null
}
if (test-path $regKey) {
$regProps = get-item $regKey | select -expand Property
if ($regProps -contains $script:RestartRegistryProperty) {
$entry.PropertyValue = Get-ItemProperty -path $regKey | select -expand $script:RestartRegistryProperty
}
}
Add-Member -inputObject $entry -memberType ScriptProperty -Name StringRepr -Value {
"Key: $($this.RegistryKey), Property: $($this.RegistryProperty), Value: $($this.PropertyValue)"
}
$entries += @($entry)
}
return $entries
} }
function Remove-RestartRegistryEntries { function Remove-RestartScheduledTask {
[cmdletbinding()] param() [cmdletbinding()] param(
foreach ($key in $script:RestartRegistryKeys.Keys) { [string] $taskName = $ScriptProductName
try { Remove-ItemProperty -Path $script:RestartRegistryKeys[$key] -name $script:RestartRegistryProperty} catch {} )
$existingTask = Get-RestartScheduledTask -taskName $taskName
if ($existingTask) {
Write-EventLogWrapper -message "Found existing task named '$taskName'; deleting..."
Unregister-ScheduledTask -InputObject $existingTask -Confirm:$false | out-null
}
else {
Write-EventLogWrapper -message "Did not find any existing task named '$taskName'"
} }
} }
@ -221,7 +264,7 @@ function Install-SevenZip {
Write-EventLogWrapper "Downloaded '$($URLs.SevenZipDownload.$OSArch)' to '$szDlPath', now running msiexec..." Write-EventLogWrapper "Downloaded '$($URLs.SevenZipDownload.$OSArch)' to '$szDlPath', now running msiexec..."
$msiCall = '& msiexec /qn /i "{0}"' -f $szDlPath $msiCall = '& msiexec /qn /i "{0}"' -f $szDlPath
# Windows suxxx so msiexec sometimes returns right away? or something idk. fuck # Windows suxxx so msiexec sometimes returns right away? or something idk. fuck
Invoke-ExpressionAndLog -checkExitCode -command $msiCall -sleepSeconds 30 Invoke-ExpressionEx -checkExitCode -command $msiCall -sleepSeconds 30
} }
finally { finally {
rm -force $szDlPath rm -force $szDlPath
@ -242,9 +285,9 @@ function Install-VBoxAdditions {
Write-EventLogWrapper "Installing the Oracle certificate..." Write-EventLogWrapper "Installing the Oracle certificate..."
$oracleCert = resolve-path "$baseDir\cert\oracle-vbox.cer" | select -expand path $oracleCert = resolve-path "$baseDir\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 # NOTE: Checking for exit code, but this command will fail with an error if the cert is already installed
Invoke-ExpressionAndLog -checkExitCode -command ('& "{0}" add-trusted-publisher "{1}" --root "{1}"' -f "$baseDir\cert\VBoxCertUtil.exe",$oracleCert) Invoke-ExpressionEx -checkExitCode -command ('& "{0}" add-trusted-publisher "{1}" --root "{1}"' -f "$baseDir\cert\VBoxCertUtil.exe",$oracleCert)
Write-EventLogWrapper "Installing the virtualbox additions" Write-EventLogWrapper "Installing the virtualbox additions"
Invoke-ExpressionAndLog -checkExitCode -command ('& "{0}" /with_wddm /S' -f "$baseDir\VBoxWindowsAdditions.exe") # returns IMMEDIATELY, goddamn fuckers Invoke-ExpressionEx -checkExitCode -command ('& "{0}" /with_wddm /S' -f "$baseDir\VBoxWindowsAdditions.exe") # returns IMMEDIATELY, goddamn fuckers
while (get-process -Name VBoxWindowsAdditions*) { write-host 'Waiting for VBox install to finish...'; sleep 1; } while (get-process -Name VBoxWindowsAdditions*) { write-host 'Waiting for VBox install to finish...'; sleep 1; }
Write-EventLogWrapper "virtualbox additions have now been installed" Write-EventLogWrapper "virtualbox additions have now been installed"
} }
@ -255,7 +298,7 @@ function Install-VBoxAdditions {
$vbgaPath = mkdir -force "${env:Temp}\InstallVbox" | select -expand fullname $vbgaPath = mkdir -force "${env:Temp}\InstallVbox" | select -expand fullname
try { try {
Write-EventLogWrapper "Extracting iso at '$isoPath' to directory at '$vbgaPath'..." Write-EventLogWrapper "Extracting iso at '$isoPath' to directory at '$vbgaPath'..."
Invoke-ExpressionAndLog -checkExitCode -command ('sevenzip x "{0}" -o"{1}"' -f $isoPath, $vbgaPath) Invoke-ExpressionEx -checkExitCode -command ('sevenzip x "{0}" -o"{1}"' -f $isoPath, $vbgaPath)
InstallVBoxAdditionsFromDir $vbgaPath InstallVBoxAdditionsFromDir $vbgaPath
} }
finally { finally {
@ -292,15 +335,21 @@ function Install-CompiledDotNetAssemblies {
# http://support.microsoft.com/kb/2570538 # http://support.microsoft.com/kb/2570538
# http://robrelyea.wordpress.com/2007/07/13/may-be-helpful-ngen-exe-executequeueditems/ # http://robrelyea.wordpress.com/2007/07/13/may-be-helpful-ngen-exe-executequeueditems/
# Don't check the return value - sometimes it fails and that's fine # Don't check the return value - sometimes it fails and that's fine
set-alias ngen32 "${env:WinDir}\microsoft.net\framework\v4.0.30319\ngen.exe" $ngen32 = "${env:WinDir}\microsoft.net\framework\v4.0.30319\ngen.exe"
ngen32 update /force /queue Invoke-ExpressionEx "$ngen32 update /force /queue"
ngen32 executequeueditems Invoke-ExpressionEx "$ngen32 executequeueditems"
# set-alias ngen32 "${env:WinDir}\microsoft.net\framework\v4.0.30319\ngen.exe"
# ngen32 update /force /queue
# ngen32 executequeueditems
if ((Get-OSArchitecture) -match $ArchitectureId.amd64) { if ((Get-OSArchitecture) -match $ArchitectureId.amd64) {
set-alias ngen64 "${env:WinDir}\microsoft.net\framework64\v4.0.30319\ngen.exe" # set-alias ngen64 "${env:WinDir}\microsoft.net\framework64\v4.0.30319\ngen.exe"
ngen64 update /force /queue # ngen64 update /force /queue
ngen64 executequeueditems # ngen64 executequeueditems
$ngen64 = "${env:WinDir}\microsoft.net\framework64\v4.0.30319\ngen.exe"
Invoke-ExpressionEx "$ngen64 update /force /queue"
Invoke-ExpressionEx "$ngen64 executequeueditems"
} }
} }
@ -310,19 +359,19 @@ function Compress-WindowsInstall {
$udfZipPath = Get-WebUrl -url $URLs.UltraDefragDownload.$OSArch -outDir $env:temp $udfZipPath = Get-WebUrl -url $URLs.UltraDefragDownload.$OSArch -outDir $env:temp
$udfExPath = "${env:temp}\ultradefrag-portable-6.1.0.$OSArch" $udfExPath = "${env:temp}\ultradefrag-portable-6.1.0.$OSArch"
# This archive contains a folder - extract it directly to the temp dir # This archive contains a folder - extract it directly to the temp dir
Invoke-ExpressionAndLog -checkExitCode -command ('sevenzip x "{0}" "-o{1}"' -f $udfZipPath,$env:temp) Invoke-ExpressionEx -checkExitCode -command ('sevenzip x "{0}" "-o{1}"' -f $udfZipPath,$env:temp)
$sdZipPath = Get-WebUrl -url $URLs.SdeleteDownload -outDir $env:temp $sdZipPath = Get-WebUrl -url $URLs.SdeleteDownload -outDir $env:temp
$sdExPath = "${env:temp}\SDelete" $sdExPath = "${env:temp}\SDelete"
# This archive does NOT contain a folder - extract it to a subfolder (will create if necessary) # This archive does NOT contain a folder - extract it to a subfolder (will create if necessary)
Invoke-ExpressionAndLog -checkExitCode -command ('sevenzip x "{0}" "-o{1}"' -f $sdZipPath,$sdExPath) Invoke-ExpressionEx -checkExitCode -command ('sevenzip x "{0}" "-o{1}"' -f $sdZipPath,$sdExPath)
stop-service wuauserv stop-service wuauserv
rm -recurse -force ${env:WinDir}\SoftwareDistribution\Download rm -recurse -force ${env:WinDir}\SoftwareDistribution\Download
start-service wuauserv start-service wuauserv
Invoke-ExpressionAndLog -checkExitCode -command ('& {0} --optimize --repeat "{1}"' -f "$udfExPath\udefrag.exe","$env:SystemDrive") Invoke-ExpressionEx -checkExitCode -logToStdout -command ('& {0} --optimize --repeat "{1}"' -f "$udfExPath\udefrag.exe","$env:SystemDrive")
Invoke-ExpressionAndLog -checkExitCode -command ('& {0} /accepteula -q -z "{1}"' -f "$sdExPath\SDelete.exe",$env:SystemDrive) Invoke-ExpressionEx -checkExitCode -command ('& {0} /accepteula -q -z "{1}"' -f "$sdExPath\SDelete.exe",$env:SystemDrive)
} }
finally { finally {
rm -recurse -force $udfZipPath,$udfExPath,$sdZipPath,$sdExPath -ErrorAction Continue rm -recurse -force $udfZipPath,$udfExPath,$sdZipPath,$sdExPath -ErrorAction Continue
@ -494,21 +543,21 @@ function Enable-WinRM {
# call cmd.exe over and over like this, that problem goes away. # call cmd.exe over and over like this, that problem goes away.
# Note: order is important. This order makes sure that any time packer can successfully # Note: order is important. This order makes sure that any time packer can successfully
# connect to WinRm, it won't later turn winrm back off or make it unavailable. # connect to WinRm, it won't later turn winrm back off or make it unavailable.
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'net stop winrm' Invoke-ExpressionEx -invokeWithCmdExe -command 'net stop winrm'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'sc.exe config winrm start= auto' Invoke-ExpressionEx -invokeWithCmdExe -command 'sc.exe config winrm start= auto'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'winrm quickconfig -q' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm quickconfig -q'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'winrm quickconfig -transport:http' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm quickconfig -transport:http'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'winrm set winrm/config @{MaxTimeoutms="1800000"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config @{MaxTimeoutms="1800000"}'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'winrm set winrm/config/winrs @{MaxMemoryPerShellMB="2048"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/winrs @{MaxMemoryPerShellMB="2048"}'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'winrm set winrm/config/service @{AllowUnencrypted="true"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/service @{AllowUnencrypted="true"}'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'winrm set winrm/config/client @{AllowUnencrypted="true"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/client @{AllowUnencrypted="true"}'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'winrm set winrm/config/service/auth @{Basic="true"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/service/auth @{Basic="true"}'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'winrm set winrm/config/client/auth @{Basic="true"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/client/auth @{Basic="true"}'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'winrm set winrm/config/service/auth @{CredSSP="true"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/service/auth @{CredSSP="true"}'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="5985"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="5985"}'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'netsh advfirewall firewall set rule group="remote administration" new enable=yes' Invoke-ExpressionEx -invokeWithCmdExe -command 'netsh advfirewall firewall set rule group="remote administration" new enable=yes'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'netsh firewall add portopening TCP 5985 "Port 5985"' Invoke-ExpressionEx -invokeWithCmdExe -command 'netsh firewall add portopening TCP 5985 "Port 5985"'
Invoke-ExpressionAndLog -invokeWithCmdExe -command 'net start winrm' Invoke-ExpressionEx -invokeWithCmdExe -command 'net start winrm'
} }
function Set-PasswordExpiry { # TODO fixme use pure Powershell function Set-PasswordExpiry { # TODO fixme use pure Powershell
@ -521,7 +570,7 @@ function Set-PasswordExpiry { # TODO fixme use pure Powershell
wmic useraccount where "name='{0}'" set "PasswordExpires={1}" wmic useraccount where "name='{0}'" set "PasswordExpires={1}"
"@ "@
$command = $command -f $accountName, $passwordExpiress $command = $command -f $accountName, $passwordExpiress
Invoke-ExpressionAndLog -invokeWithCmdExe -command $command Invoke-ExpressionEx -invokeWithCmdExe -command $command
} }
<# <#

Loading…
Cancel
Save