param( [String] $ScriptProductName = "PostInstall-Marionettist", [String] $ScriptPath = $MyInvocation.MyCommand.Path, [String] $ScriptName = $MyInvocation.MyCommand.Name ) ### Global Constants that I use elsewhere $ArchitectureId = @{ amd64 = "amd64" i386 = "i386" } $WindowsVersionId = @{ w81 = "w81" w10 = "w10" w10ltsb = "w10ltsb" server2012r2 = "server2012r2" } $URLs = @{ 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" WindowsIsoDownload = @{ $WindowsVersionId.w81 = @{ $ArchitectureId.i386 = @{ URL = "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" SHA1 = "4ddd0881779e89d197cb12c684adf47fd5d9e540" } $ArchitectureId.amd64 = @{ URL = "http://download.microsoft.com/download/B/9/9/B999286E-0A47-406D-8B3D-5B5AD7373A4A/9600.16384.WINBLUE_RTM.130821-1623_X64FRE_ENTERPRISE_EVAL_EN-US-IRM_CENA_X64FREE_EN-US_DV5.ISO" SHA1 = "5e4ecb86fd8619641f1d58f96e8561ec" } } $WindowsVersionId.w10 = @{ $ArchitectureId.i386 = @{ URL = "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" SHA1 = "875b450d67e7176b8b3c72a80c60a0628bf1afac" } $ArchitectureId.amd64 = @{ URL = "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" SHA1 = "56ab095075be28a90bc0b510835280975c6bb2ce" } } } } $script:ScriptPath = $MyInvocation.MyCommand.Path ### Private support functions I use behind the scenes <# .description Add a line to a file idempotently; that is, if the line is not already present in the file, add it, but if it is already present, then do nothing #> function Add-FileLineIdempotently { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [String] $file, [Parameter(Mandatory=$true)] [String[]] $newLine, [Parameter(Mandatory=$true)] [String] $encoding = "UTF8" ) if (-not (Test-Path $file)) { New-Item -ItemType File -Path $file | Out-Null } $origContents = Get-Content $file $newLine |% { if ($origContents -notcontains $_) { Out-File -FilePath $file -InputObject $_ -Encoding $encoding -Append } } } <# .description Do some very basic filename sanitization #> function Get-SanitizedFilename { [cmdletbinding()] param( [Parameter(Mandatory=$true)] [String] $fileName ) $invalidChars = [System.IO.Path]::GetInvalidFileNameChars() $replacementCharacter = "_" $newName = [System.String]::Copy($fileName) foreach ($invChar in $invalidChars) { $newName = $newName.Replace($invChar, $replacementCharacter) } return $newName } <# .synopsis Get a rooted path .notes Especially useful for .NET functions, which don't understand Powershell's $pwd, and instead have their own concept of the working directory, which is (in any normal case) always %USERPROFILE%. This means that if you do this: cd C:\Windows (New-Object System.Net.WebClient).DownloadFile("http://example.com/file.txt", "./file.txt") ... the file will be downloaded to %USERPROFILE%\file.txt, not C:\Windows\file.txt #> function Get-RootedPath { [cmdletbinding()] param( [Parameter(Mandatory=$true)] [String] $path ) if (-not [System.IO.Path]::IsPathRooted($path)) { $path = Join-Path -Path $pwd -ChildPath $path } try { $rootedPath = [System.IO.Path]::GetFullPath($path) } catch { Write-Error "Failed to validate path '$path'" throw $_ } return $rootedPath } <# .synopsis Download a URL from the web .parameter url The URL to download .parameter outDir Save the file to this directory. The filename will be the last part of the URL. This will make sense for a basic case like http://example.com/file.txt, but might be a little ugly for URLs like http://example.com/?product=exampleProduct&version=exampleVersion .parameter outFile Save the file to this exact filename. .notes Why not use Invoke-WebRequest or Invoke-RestMethod? Because those are not available before Powershell 3.0, and I still want to be able to use this function on vanilla Windows 7 (hopefully just before applying all updates and getting a more recent Powershell, but still.) #> 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") { # If the URL is http://example.com/whatever/somefile.txt, the last URL component is somefile.txt $lastUrlComponent = [System.IO.Path]::GetFileName($url) $filename = Get-SanitizedFilename -fileName $lastUrlComponent $outFile = Join-Path -Path $outDir -ChildPath $fileName } $outFile = Get-RootedPath $outFile Write-EventLogWrapper "Downloading '$url' to '$outFile'..." (New-Object System.Net.WebClient).DownloadFile($url, $outFile) return (Get-Item $outFile) } <# .synopsis Invoke an expression; log the expression, optionally with any output, and the last exit code if appropriate #> function Invoke-ExpressionEx { [cmdletbinding()] param( [parameter(mandatory=$true)] [string] $command, [switch] $invokeWithCmdExe, [switch] $checkExitCode, [switch] $logToStdout, [int] $sleepSeconds ) $global:LASTEXITCODE = 0 if ($invokeWithCmdExe) { $commandSb = {cmd /c "$command"}.GetNewClosure() } else { $commandSb = {invoke-expression -command $command}.GetNewClosure() } Write-EventLogWrapper "Invoke-ExpressionEx called to run command '$command'`r`n`r`nUsing scriptblock: $($commandSb.ToString())" $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 } catch { Write-EventLogWrapper -message "Invoke-ExpressionEx failed to run command '$command'" Write-ErrorStackToEventLog -errorStack $_ throw $_ } if ($checkExitCode -and $global:LASTEXITCODE -ne 0) { throw "LASTEXITCODE: ${global:LASTEXITCODE} for command: '${command}'" } if ($sleepSeconds) { start-sleep $sleepSeconds } } ### Publicly exported functions called directly from slipstreaming scripts <# .synopsis Create a temporary directory #> function New-TemporaryDirectory { $dirPath = [System.IO.Path]::GetTempFileName() # creates a file automatically rm $dirPath mkdir $dirPath # mkdir returns a DirectoryInfo object; not capturing it here returns it to the caller } <# .synopsis Return an object containing metadata for the trial ISO for a particular version of Windows .notes TODO: this sucks but I can't think of anything better to do #> function Get-WindowsTrialISO { [cmdletbinding()] param( $WindowsVersion = ([Environment]::OSVersion.Version), $WindowsArchitecture = (Get-OSArchitecture) ) if ($WindowsVersion.Major -eq 6 -and $WindowsVersion.Minor -eq 3) { return $URLs.WindowsIsoDownload.w81.$WindowsArchitecture } elseif ($WindowsVersion.Major -eq 10 -and $WindowsVersion.Minor -eq 0) { return $URLs.WindowsIsoDownload.w10.$WindowsArchitecture } else { throw "No URL known for Windows version '$WindowsVersion' and architecture '$WindowsArchitecture'" } } <# .synopsis Wrapper that writes to the event log but also to the screen #> function Write-EventLogWrapper { [cmdletbinding()] param( [parameter(mandatory=$true)] [String] $message, [int] $eventId = 0, [ValidateSet("Error",'Warning','Information','SuccessAudit','FailureAudit')] $entryType = "Information", [String] $EventLogName = $ScriptProductName, [String] $EventLogSource = $ScriptName ) if (-not (get-eventlog -logname * |? { $_.Log -eq $eventLogName })) { New-EventLog -Source $EventLogSource -LogName $eventLogName } $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 darkgray (get-date -Format "yyyy-MM-dd HH:mm:ss") # The event log tracks the date, but writing to host never shows it write-host -foreground darkgray "$messagePlus`r`n" Write-EventLog -LogName $eventLogName -Source $EventLogSource -EventID $eventId -EntryType $entryType -Message $MessagePlus } <# .synopsis Invoke a scriptblock. If it throws, write the errors out to the event log and exist with an error code .notes This is intended to be a handy wrapper for calling functions in this module that takes care of logging an exception for you. See the autounattend-postinstall.ps1 and provisioner-postinstall.ps1 scripts for examples. #> function Invoke-ScriptblockAndCatch { [cmdletbinding()] param( [parameter(mandatory=$true)] [ScriptBlock] $scriptBlock, [int] $failureExitCode = 666 ) try { Invoke-Command $scriptBlock } catch { Write-ErrorStackToEventLog -errorStack $error exit $failureExitCode } } function Write-ErrorStackToEventLog { [cmdletbinding()] param( [parameter(mandatory=$true)] $errorStack ) $message = "======== CAUGHT EXCEPTION ========`r`n$errorStack`r`n" $message += "======== ERROR STACK ========`r`n" $errorStack |% { $message += "$_`r`n----`r`n" } $message += "======== ========" Write-EventLogWrapper $message } function Test-PowershellSyntax { [cmdletbinding(DefaultParameterSetName='FromText')] param( [parameter(mandatory=$true,ParameterSetName='FromText')] [string] $text, [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 ($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 } <# .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 modify specific battery arguments without importing XML (not gonna do.dat). Modify it here: $settings = New-ScheduledTaskSettingsSet -allowStartIfonBatteries -dontStopIfGoingOnBatteries # 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 -settings $settings -action $action -trigger $trigger $message = "Created scheduled task called '$taskName', which will run a temp file at '$tempRestartScriptPath', containing:`r`n`r`n" $message += (Get-Content $tempRestartScriptPath) -join "`r`n" Write-EventLogWrapper -message $message } function Get-RestartScheduledTask { [cmdletbinding()] param( [string] $taskName = $ScriptProductName ) Get-ScheduledTask |? -Property TaskName -match $taskName } function Remove-RestartScheduledTask { [cmdletbinding()] param( [string] $taskName = $ScriptProductName ) $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'" } } <# .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-EventLogWrapper "Downloaded '$($URLs.SevenZipDownload.$OSArch)' to '$szDlPath', now running msiexec..." $msiCall = '& msiexec /qn /i "{0}"' -f $szDlPath # Windows suxxx so msiexec sometimes returns right away? or something idk. fuck Invoke-ExpressionEx -checkExitCode -command $msiCall -sleepSeconds 30 } finally { rm -force $szDlPath } } set-alias sevenzip "${env:ProgramFiles}\7-Zip\7z.exe" function Install-VBoxAdditions { [cmdletbinding(DefaultParameterSetName="InstallFromDisc")] param( [parameter(ParameterSetName="InstallFromIsoPath",mandatory=$true)] [string] $isoPath, [parameter(ParameterSetName="InstallFromDisc",mandatory=$true)] [switch] $fromDisc ) function InstallVBoxAdditionsFromDir { param([Parameter(Mandatory=$true)][String]$baseDir) $baseDir = resolve-path $baseDir | select -expand Path Write-EventLogWrapper "Installing VBox Additions from '$baseDir'" Write-EventLogWrapper "Installing the Oracle certificate..." $oracleCert = resolve-path "$baseDir\cert\*sha*" | select -expand path foreach($cert in $oracleCert) { Invoke-ExpressionEx -checkExitCode -command ('& "{0}" add-trusted-publisher "{1}" --root "{1}"' -f "$baseDir\cert\VBoxCertUtil.exe",$cert) } # NOTE: Checking for exit code, but this command will fail with an error if the cert is already installed Write-EventLogWrapper "Installing the virtualbox additions" 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; } Write-EventLogWrapper "virtualbox additions have now been installed" } switch ($PSCmdlet.ParameterSetName) { "InstallFromIsoPath" { $isoPath = resolve-path $isoPath | select -expand Path $vbgaPath = mkdir -force "${env:Temp}\InstallVbox" | select -expand fullname try { Write-EventLogWrapper "Extracting iso at '$isoPath' to directory at '$vbgaPath'..." Invoke-ExpressionEx -checkExitCode -command ('sevenzip x "{0}" -o"{1}"' -f $isoPath, $vbgaPath) InstallVBoxAdditionsFromDir $vbgaPath } finally { rm -recurse -force $vbgaPath } } "InstallFromDisc" { $vboxDiskDrive = get-psdrive -PSProvider Filesystem |? { test-path "$($_.Root)\VBoxWindowsAdditions.exe" } if ($vboxDiskDrive) { Write-EventLogWrapper "Found VBox Windows Additions disc at $vboxDiskDrive" InstallVBoxAdditionsFromDir $vboxDiskDrive.Root } else { $message = "Could not find VBox Windows Additions disc" Write-EventLogWrapper $message throw $message } } } } function Set-AutoAdminLogon { [CmdletBinding(DefaultParameterSetName="Enable")] param( [Parameter(Mandatory=$true,ParameterSetName="Enable")] [String] $Username, [Parameter(Mandatory=$true,ParameterSetName="Enable")] [String] $Password, [Parameter(Mandatory=$true,ParameterSetName="Disable")] [Switch] $Disable ) if ($PsCmdlet.ParameterSetName -Match "Disable") { Write-EventLogWrapper "Disabling auto admin logon" $AutoAdminLogon = 0 $Username = "" $Password = "" } elseif ($PsCmdlet.ParameterSetName -Match "Enable") { Write-EventLogWrapper "Enabling auto admin logon for user '$Username'" $AutoAdminLogon = 1 } else { throw "Invalid parameter set name" } $winLogonKey = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" Set-ItemProperty -Path $winLogonKey -Name "AutoAdminLogon" -Value $AutoAdminLogon Set-ItemProperty -Path $winLogonKey -Name "DefaultUserName" -Value $Username Set-ItemProperty -Path $winLogonKey -Name "DefaultPassword" -Value $Password } function Enable-RDP { Write-EventLogWrapper "Enabling RDP" 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/ # Don't check the return value - sometimes it fails and that's fine $ngen32path = "${env:WinDir}\microsoft.net\framework\v4.0.30319\ngen.exe" # Invoke-ExpressionEx "$ngen32path update /force /queue" # Invoke-ExpressionEx "$ngen32path executequeueditems" set-alias ngen32 $ngen32path ngen32 update /force /queue ngen32 executequeueditems if ((Get-OSArchitecture) -match $ArchitectureId.amd64) { $ngen64path = "${env:WinDir}\microsoft.net\framework64\v4.0.30319\ngen.exe" # Invoke-ExpressionEx "$ngen64path update /force /queue" # Invoke-ExpressionEx "$ngen64path executequeueditems" set-alias ngen64 $ngen64path ngen64 update /force /queue 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.$OSArch" # This archive contains a folder - extract it directly to the temp dir Invoke-ExpressionEx -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-ExpressionEx -command ('sevenzip x "{0}" "-o{1}"' -f $sdZipPath,$sdExPath) stop-service wuauserv rm -recurse -force ${env:WinDir}\SoftwareDistribution\Download start-service wuauserv Invoke-ExpressionEx -logToStdout -command ('& {0} --optimize --repeat "{1}"' -f "$udfExPath\udefrag.exe","$env:SystemDrive") Invoke-ExpressionEx -command ('& {0} /accepteula -q -z "{1}"' -f "$sdExPath\SDelete.exe",$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-EventLogWrapper "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,"") | out-null start-service wuauserv } function Install-Chocolatey { [cmdletbinding()] param() $chocoExePath = "${env:ProgramData}\Chocolatey\bin" if ($($env:Path).ToLower().Contains($($chocoExePath).ToLower())) { Write-EventLogWrapper "Attempting to install Chocolatey but it's 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" } # TODO: capture and log output $chocoOutput = iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1')) Write-EventLogWrapper "Chocolatey install process completed:`r`n`r`n$chocoOutput" } function Get-FirefoxInstallDirectory { [cmdletbinding()] param() @($env:ProgramFiles,${env:ProgramFiles(x86)}) |% { $testPath = "$_\Mozilla Firefox" if (Test-Path "$testPath\firefox.exe") {$ffDir = $testPath} } if (-not $ffDir) { throw "Could not find the Firefox install location." } else { return $ffDir } } <# .notes One nice thing about FF and Chrome is that you don't have to handle updates yourself - they both install services that update the browser for you #> function Install-Firefox { [cmdletbinding()] param( [ValidateSet("Standard","ESR")] [String] $edition = "Standard", [String] $language = "en-US" ) switch ($edition) { "Standard" {$downloadPageUrl = 'https://www.mozilla.org/en-US/firefox/all/'} "ESR" {$downloadPageUrl = 'https://www.mozilla.org/en-US/firefox/organizations/all'} } $osarch = Get-OSArchitecture switch ($osarch) { $ArchitectureId.amd64 {$os = 'win64'} $ArchitectureId.i386 {$os = 'win'} } $firefoxIniFile = "${env:temp}\firefox-installer.ini" try { Write-EventLogWrapper "Finding download location for $edition edition of Firefox..." $response = Invoke-WebRequest -Uri $downloadPageUrl $downloadUrl = $response.ParsedHtml.getElementById($language).getElementsByClassName("download $os")[0].getElementsByTagName('a') | Select -Expand href $firefoxInstallerFile = Get-WebUrl -url $downloadUrl -outFile "${env:temp}\firefox-installer.exe" $firefoxIniContents = @( "QuickLaunchShortcut=false" "DesktopShortcut=false" ) Out-File -FilePath $firefoxIniFile -InputObject $firefoxIniContents -Encoding UTF8 Write-EventLogWrapper "Beginning Firefox installation process..." $process = Start-Process -FilePath $firefoxInstallerFile.FullName -ArgumentList @("/INI=`"$firefoxIniFile`"") -Wait -PassThru if ($process.ExitCode -ne 0) { throw "Firefox installer at $($firefoxInstallerFile.FullName) exited with code $($process.ExitCode)" } } catch { @($firefoxInstallerFile,$firefoxIniFile) |% { if ($_ -and (Test-Path $_)) { Remove-Item $_ } } throw $_ } Write-EventLogWrapper "Firefox installation process complete" Remove-Item @($firefoxInstallerFile,$firefoxIniFile) } function Uninstall-Firefox { [cmdletbinding()] param() $ffDir = Get-FirefoxInstallDirectory $ffUninstallHelper = Get-Item "$ffDir\uninstall\helper.exe" $process = Start-Process -FilePath $ffUninstallHelper.FullName -ArgumentList "/S" -Wait -PassThru if ($process.ExitCode -ne 0) { throw "Firefox uninstall helper at $($ffUninstallHelper.FullName) exited with code $($process.ExitCode)" } Remove-Item -Recurse -Force $ffDir } <# .parameter systemDisableImportWizard Don't run the import wizard when starting Firefox for the first time .parameter systemDisableWhatsNew Don't open dumb tabs that no one needs when starting Firefox for the first time See also: http://kb.mozillazine.org/Browser.startup.homepage_override.mstone .parameter systemEnableGlobalAddOns By default, add-ons that are installed to the global Firefox application directory are available to users, but disabled by default. Enable them by default instead. .parameter userDeleteConfiguration Wipe out the configuration, including profiles, of the current user .parameter userSetDefaultBrowser Set Firefox to be the default browser for the current user .notes Parameters prepended with "system" affect all Firefox users on the entire machine Parameters prepended with "user" affect only the current user's Firefox configuration #> function Set-FirefoxOptions { [cmdletbinding()] param( [switch] $systemDisableImportWizard, [switch] $systemDisableWhatsNew, [switch] $systemEnableGlobalAddOns, [string[]] $systemInstallAddOnsFromUrl, [switch] $userDeleteConfiguration, [switch] $userSetDefaultBrowser ) $ffDir = Get-FirefoxInstallDirectory $ffPath = "$ffDir\firefox.exe" function Test-LockCfgSetting { param( [String] $name, [String] $lockFile = "$(Get-FirefoxInstallDirectory)\mozilla.cfg" ) if (-not (Test-Path $lockFile)) { return $false } foreach ($line in (Get-Content $lockFile)) { if ($line -match "`"$name`"") { return $true } } return $false } function Remove-LockCfgSetting { param( [String] $name, [String] $lockFile = "$(Get-FirefoxInstallDirectory)\mozilla.cfg" ) $newLockFileContents = @() foreach ($line in (Get-Content $lockFile)) { if ($line -notmatch "`"$name`"") { $newLockFileContents += @($line) } } Out-File -InputObject $newLockFileContents -FilePath $lockFile -Encoding ASCII -Force } function Add-LockCfgSetting { param( [String] $name, $value, [String] $lockFile = "$(Get-FirefoxInstallDirectory)\mozilla.cfg" ) if ($value.GetType().FullName -match "System.Int*") { $wrappedValue = $value } else { $wrappedValue = "`"$value`"" } $newSettingLine = 'pref("{0}", {1});' -f $name, $wrappedValue if (-not (Test-Path $lockFile)) { Out-File -InputObject "//" -FilePath $lockFile -Encoding "ASCII" } if (Test-LockCfgSetting -name $name -lockFile $lockFile) {Remove-LockCfgSetting -name $name -lockFile $lockFile} Add-FileLineIdempotently -file $lockFile -Encoding ASCII -newLine $newSettingLine } <# .notes We assume that $lockPrefFile is unique to us and we can always overwrite it See also: http://kb.mozillazine.org/Locking_preferences #> function Enable-LockCfg { [cmdletbinding()] param( [String] $lockPrefFile = "$(Get-FirefoxInstallDirectory)\defaults\pref\marionettist-locked-configuration.js" ) $lockPrefContents = @( 'pref("general.config.obscure_value", 0);' # only needed if you do not want to obscure the content with ROT-13 'pref("general.config.filename", "mozilla.cfg");' ) Out-File -InputObject $lockPrefContents -FilePath $lockPrefFile -Encoding ASCII -Force } if ($systemDisableImportWizard) { $overrideIniContents = @( '[XRE]' 'EnableProfileMigrator=false' ) Out-File -InputObject $overrideIniContents -FilePath "$ffDir\browser\override.ini" -Encoding UTF8 } if ($systemDisableWhatsNew) { Add-LockCfgSetting -name "browser.startup.homepage_override.mstone" -value "ignore" Enable-LockCfg } if ($systemEnableGlobalAddOns) { $globalAddOnsPrefFile = "$ffDir\defaults\pref\marionettist-enable-global-add-ons.js" # See also: https://mike.kaply.com/2012/02/21/understanding-add-on-scopes/ Add-FileLineIdempotently -file "$globalAddOnsPrefFile" -Encoding ASCII -newLine @( 'pref("extensions.enabledScopes", "15");' 'pref("extensions.autoDisableScopes", 0);' 'pref("extensions.shownSelectionUI", true);' ) } if ($systemInstallAddOnsFromUrl) { foreach ($url in $systemInstallAddOnsFromUrl) { Install-FirefoxAddOnGlobally $url } } if ($userDeleteConfiguration) { Get-Process |? Name -eq "firefox" | Stop-Process @("${env:AppData}\Mozilla\Firefox", "${env:LocalAppData}\Mozilla\Firefox") |% { if (test-path $_) {Remove-Item -Recurse -Force $_} } } if ($userSetDefaultBrowser) { Start-Process -FilePath $ffPath -ArgumentList @("-silent", "-setDefaultBrowser") -Wait -Verb RunAs # This didn't appear to work: # $defaultBrowserPath = "HKCU:\Software\Classes\http\shell\open\command" # $defaultBrowserValue = '"{0}" -osint -url "%1"' -f $ffPath # Set-ItemProperty -path $defaultBrowserPath -name "(default)" -value $defaultBrowserValue } } <# .parameter latestDownloadUrl Obtain this parameter by going to the site for the add-on at addons.mozilla.org and copying the link from under the "Add to Firefox" button .notes Since recent versions of Firefox, you must use only signed add-ons, which typically means you have to get them from addons.mozilla.org See also: https://support.mozilla.org/en-US/questions/966922 #> function Install-FirefoxAddOnGlobally { [CmdletBinding(DefaultParameterSetName="Name")] param( [Parameter(ParameterSetName="Url", Mandatory=$true)] [String] $latestDownloadUrl, [Parameter(ParameterSetName="Name", Mandatory=$true)] [String] $addOnName ) $ffDir = Get-FirefoxInstallDirectory $ffSystemExtensionsDir = "$ffDir\browser\extensions" if ($addOnName) { $downloadPageUrl = "https://addons.mozilla.org/en-US/firefox/addon/$addOnName/" $downloadPageResponse = Invoke-WebRequest -Uri $downloadPageUrl #$downloadUrl = $downloadPageResponse.ParsedHtml.getElementById("addon").getElementsByClassName("install-button")[0].getElementsByTagName('a')[0] | Select -Expand href $addOnId = $downloadPageResponse.ParsedHtml.getElementById("addon").attributes |? { $_.nodeName -eq "data-id" } | Select -Expand nodeValue $latestDownloadUrl = "https://addons.mozilla.org/firefox/downloads/latest/$addOnId/addon-$addOnId-latest.xpi" } # Cannot install from GitHub, because the version posted there is not signed # $latestReleaseUrl = "https://api.github.com/repos/gorhill/uBlock/releases/latest" # $uboXpiInfo = Invoke-RestMethod -Uri $latestReleaseUrl | Select -Expand assets |? -Property content_type -eq "application/x-xpinstall" # $downloadedXpiPath = Get-WebUrl -url $uboXpiInfo.browser_download_url -outDir $ffSystemExtensionsDir # Instead, install from addons.mozilla.org: $downloadedXpiPath = Get-WebUrl -url $latestDownloadUrl -outDir $ffSystemExtensionsDir $tempExtractDir = Join-Path ${env:temp} $downloadedXpiPath.BaseName sevenzip x -y "-o$tempExtractDir" $downloadedXpiPath # For automatic installation, you must install the extension to a folder named after its id, which can be found in the install.rdf of the extension itself: [System.Xml.XmlDocument] $installRdfXml = Get-Content "$tempExtractDir\install.rdf" $deployedExtractDir = Join-Path $ffSystemExtensionsDir $installrdfxml.RDF.Description.id if (Test-Path $deployedExtractDir) { Remove-Item -Force -Recurse $deployedExtractDir } mv $tempExtractDir $deployedExtractDir Set-FirefoxOptions -systemEnableGlobalAddOns } <# .parameter uniquePreferenceFileName We assume that this file is unique to us and we can always overwrite it .notes See also: https://support.mozilla.org/en-US/questions/966922 #> function Install-FirefoxUBlockOrigin { [CmdletBinding()] param() $ffDir = Get-FirefoxInstallDirectory $ffSystemExtensionsDir = "$ffDir\browser\extensions" $tempExtractDir = Join-Path ${env:temp} 'uBlockOrigin' # Cannot install from GitHub, because the version posted there is not signed # $latestReleaseUrl = "https://api.github.com/repos/gorhill/uBlock/releases/latest" # $uboXpiInfo = Invoke-RestMethod -Uri $latestReleaseUrl | Select -Expand assets |? -Property content_type -eq "application/x-xpinstall" # $downloadedXpiPath = Join-Path $ffSystemExtensionsDir $uboXpiInfo.name # Get-WebUrl -url $uboXpiInfo.browser_download_url -outFile $downloadedXpiPath # Instead, install from addons.mozilla.org: Get-WebUrl -url "https://addons.mozilla.org/firefox/downloads/latest/607454/addon-607454-latest.xpi" sevenzip x -y "-o$tempExtractDir" $downloadedXpiPath # For automatic installation, you must install the extension to a folder named after its id, which can be found in the install.rdf of the extension itself: [System.Xml.XmlDocument] $installRdfXml = Get-Content "$tempExtractDir\install.rdf" $deployedExtractDir = Join-Path $ffSystemExtensionsDir $installrdfxml.RDF.Description.id if (Test-Path $deployedExtractDir) { Remove-Item -Force -Recurse $deployedExtractDir } mv $tempExtractDir $deployedExtractDir Set-FirefoxOptions -systemEnableGlobalAddOns } function Set-UserOptions { [cmdletbinding()] param( [switch] $ShowHiddenFiles, [switch] $ShowSystemFiles, [switch] $ShowFileExtensions, [switch] $ShowStatusBar, [switch] $DisableSharingWizard, [switch] $EnablePSOnWinX, [switch] $EnableQuickEdit, [switch] $DisableSystrayHide, [switch] $DisableIEFirstRunCustomize ) $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 } $explorerKey = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer' if ($DisableSystrayHide) { Set-ItemProperty -path $explorerKey -name EnableAutoTray -value 0 } $consoleKey = "HKCU:\Console" if ($EnableQuickEdit) { Set-ItemProperty -path $consoleKey -name QuickEdit -value 1 } $internetExplorerKey = "HKCU:\Software\Policies\Microsoft\Internet Explorer\Main" mkdir -Force $internetExplorerKey if ($DisableIEFirstRunCustomize) { Set-ItemProperty -path $internetExplorerKey -name DisableFirstRunCustomize -value 1 } } <# .SYNOPSIS This function are used to pin and unpin programs from the taskbar and Start-menu in Windows 7 and Windows Server 2008 R2 .DESCRIPTION The function have to parameteres which are mandatory: Action: PinToTaskbar, PinToStartMenu, UnPinFromTaskbar, UnPinFromStartMenu FilePath: The path to the program to perform the action on .notes from: https://gallery.technet.microsoft.com/scriptcenter/b66434f1-4b3f-4a94-8dc3-e406eb30b750 TODO: I hate it when things pollute the global variable space! .EXAMPLE Set-PinnedApplication -Action PinToTaskbar -FilePath "C:\WINDOWS\system32\notepad.exe" .EXAMPLE Set-PinnedApplication -Action UnPinFromTaskbar -FilePath "C:\WINDOWS\system32\notepad.exe" #> function Set-PinnedApplication { [CmdletBinding()] param( [Parameter(Mandatory=$true)][string]$Action, [Parameter(Mandatory=$true)][string]$FilePath ) if (-not (test-path $FilePath)) { throw "No file at '$FilePath'" } function InvokeVerb { param([string]$FilePath,$verb) $verb = $verb.Replace("&","") $path = split-path $FilePath $shell = new-object -com "Shell.Application" $folder = $shell.Namespace($path) $item = $folder.Parsename((split-path $FilePath -leaf)) $itemVerb = $item.Verbs() | ? {$_.Name.Replace("&","") -eq $verb} if ($itemVerb) { $itemVerb.DoIt() } else { throw "Verb $verb not found." } } function GetVerb { param([int]$verbId) try { $t = [type]"CosmosKey.Util.MuiHelper" } catch { $def = [Text.StringBuilder]"" [void]$def.AppendLine('[DllImport("user32.dll")]') [void]$def.AppendLine('public static extern int LoadString(IntPtr h,uint id, System.Text.StringBuilder sb,int maxBuffer);') [void]$def.AppendLine('[DllImport("kernel32.dll")]') [void]$def.AppendLine('public static extern IntPtr LoadLibrary(string s);') add-type -MemberDefinition $def.ToString() -name MuiHelper -namespace CosmosKey.Util } if($global:CosmosKey_Utils_MuiHelper_Shell32 -eq $null){ $global:CosmosKey_Utils_MuiHelper_Shell32 = [CosmosKey.Util.MuiHelper]::LoadLibrary("shell32.dll") } $maxVerbLength = 255 $verbBuilder = new-object Text.StringBuilder "",$maxVerbLength [void][CosmosKey.Util.MuiHelper]::LoadString($CosmosKey_Utils_MuiHelper_Shell32,$verbId,$verbBuilder,$maxVerbLength) return $verbBuilder.ToString() } $verbs = @{ "PintoStartMenu"=5381 "UnpinfromStartMenu"=5382 "PintoTaskbar"=5386 "UnpinfromTaskbar"=5387 } if ($verbs.$Action -eq $null) { throw "Action $action not supported`nSupported actions are:`n`tPintoStartMenu`n`tUnpinfromStartMenu`n`tPintoTaskbar`n`tUnpinfromTaskbar" } InvokeVerb -FilePath $FilePath -Verb $(GetVerb -VerbId $verbs.$action) } function Disable-HibernationFile { [cmdletbinding()] param() Write-EventLogWrapper "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 } <# .synopsis Forcibly enable WinRM .notes TODO: Rewrite in pure Powershell #> function Enable-WinRM { [cmdletbinding()] param() Write-EventLogWrapper "Enabling WinRM..." # I've had the best luck doing it this way - NOT doing it in a single batch script # Sometimes one of these commands will stop further execution in a batch script, but when I # 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 # connect to WinRm, it won't later turn winrm back off or make it unavailable. Invoke-ExpressionEx -invokeWithCmdExe -command 'net stop winrm' Invoke-ExpressionEx -invokeWithCmdExe -command 'sc.exe config winrm start= auto' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm quickconfig -q' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm quickconfig -transport:http' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config @{MaxTimeoutms="1800000"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/winrs @{MaxMemoryPerShellMB="2048"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/service @{AllowUnencrypted="true"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/client @{AllowUnencrypted="true"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/service/auth @{Basic="true"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/client/auth @{Basic="true"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/service/auth @{CredSSP="true"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="5985"}' Invoke-ExpressionEx -invokeWithCmdExe -command 'netsh advfirewall firewall set rule group="remote administration" new enable=yes' Invoke-ExpressionEx -invokeWithCmdExe -command 'netsh firewall add portopening TCP 5985 "Port 5985"' Invoke-ExpressionEx -invokeWithCmdExe -command 'net start winrm' } function Add-LocalSamUser { [cmdletbinding()] param( [Parameter(Mandatory=$true)] [string] $userName, [Parameter(Mandatory=$true)] [string] $password, [string] $fullName, [switch] $PassThru ) Write-EventLogWrapper "Creating a new local user called '$userName'" $computer = [ADSI]"WinNT://$env:COMPUTERNAME,Computer" $newUser = $computer.Create("User", $userName) $newUser.SetPassword($password) $newUser.SetInfo() $newUser.FullName = $fullName $newUser.SetInfo() Add-LocalSamUserToGroup -userName $userName -groupName "Users" if ($PassThru) { return $newUser } } function Add-LocalSamUserToGroup { [cmdletbinding()] param( [parameter(mandatory=$true)] [string] $userName, [parameter(mandatory=$true)] [string] $groupName ) Write-EventLogWrapper "Adding '$userName' to the local '$groupName' group" $localAdmins = [ADSI]"WinNT://$env:COMPUTERNAME/$groupName,group" $localAdmins.Add("WinNT://$userName") } function Set-PasswordExpiry { # TODO fixme use pure Powershell [cmdletbinding()] param( [parameter(mandatory=$true)] [string] $accountName, [parameter(mandatory=$true,ParameterSetName="EnablePasswordExpiry")] [switch] $enable, [parameter(mandatory=$true,ParameterSetName="DisablePasswordExpiry")] [switch] $disable ) $passwordExpires = if ($PsCmdlet.ParameterSetName -match "EnablePasswordExpiry") {"TRUE"} else {"FALSE"} $command = @" wmic useraccount where "name='{0}'" set "PasswordExpires={1}" "@ $command = $command -f $accountName,$passwordExpires Invoke-ExpressionEx -command $command } <# .synopsis Set all attached networks to Private .description (On some OSes) you cannot enable Windows PowerShell Remoting on network connections that are set to Public Spin through all the network locations and if they are set to Public, set them to Private using the INetwork interface: http://msdn.microsoft.com/en-us/library/windows/desktop/aa370750(v=vs.85).aspx For more info, see: http://blogs.msdn.com/b/powershell/archive/2009/04/03/setting-network-location-to-private.aspx #> function Set-AllNetworksToPrivate { [cmdletbinding()] param() # Network location feature was only introduced in Windows Vista - no need to bother with this # if the operating system is older than Vista if([environment]::OSVersion.version.Major -lt 6) { Write-EventLogWrapper "Set-AllNetworksToPrivate: Running on pre-Vista machine, no changes necessary" return } Write-EventLogWrapper "Setting all networks to private..." if(1,3,4,5 -contains (Get-WmiObject win32_computersystem).DomainRole) { throw "Cannot change network location on a domain-joined computer" } # Disable the GUI which will modally pop up (at least on Win10) lol New-Item "HKLM:\System\CurrentControlSet\Control\Network\NewNetworkWindowOff" -force | out-null # Get network connections $networkListManager = [Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]"{DCB00C01-570F-4A9B-8D69-199FDBA5723B}")) foreach ($connection in $networkListManager.GetNetworkConnections()) { $connName = $connection.GetNetwork().GetName() $oldCategory = $connection.GetNetwork().GetCategory() $connection.getNetwork().SetCategory(1) $newCategory = $connection.GetNetwork().GetCategory() Write-EventLogWrapper "Changed connection category for '$connName' from '$oldCategory' to '$newCategory'" } } <# function Get-PowerScheme { [cmdletbinding(DefaultParameterSetName("Active"))] param( [Parameter(Mandatory=$true,ParameterSetName="Active")] [switch] $Active, [Parameter(Mandatory=$true,ParameterSetName="ByGuid")] [switch] $ByGuid, [Parameter(Mandatory=$true,ParameterSetName="ByName")] [switch] $ByName, ) $powerScheme = New-Object PSObject -Property @{Name="";GUID="";} $psre = '^Power Scheme GUID\:\s+([A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12})\s+\((.*)\)' switch ($PsCmdlet.ParameterSetName) { "Active" { $activeSchemeString = powercfg /getactivescheme if ($activeSchemeString -match $psre) { $powerScheme.Name = $matches[2] $powerScheme.GUID = $matches[1] } else { write-error "Error: could not find active power configuration"} } "ByGuid" { foreach ($powerSchemeString in (powercfg /list)) { $ } } "ByName" {} default {write-error "Error: not sure how to process a parameter set named $($PsCmdlet.ParameterSetName)"} } return $powerScheme } #> <# .synopsis Set the idle time that must elapse before Windows will power off a display .parameter seconds The number of seconds before poweroff. A value of 0 means never power off. .notes AFAIK, this cannot be done without shelling out to powercfg #> function Set-IdleDisplayPoweroffTime { [cmdletbinding()] param( [parameter(mandatory=$true)] [int] $seconds ) $currentScheme = (powercfg /getactivescheme).split()[3] $DisplaySubgroupGUID = "7516b95f-f776-4464-8c53-06167f40cc99" $TurnOffAfterGUID = "3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e" set-alias powercfg "${env:SystemRoot}\System32\powercfg.exe" powercfg /setacvalueindex $currentScheme $DisplaySubgroupGUID $TurnOffAfterGUID 0 } <# .description Allow connecting to HTTPS WinRM servers (used with, for example, Enter-PSSession) without checking the certificate. This is not recommended, but can be useful for non-domain-joined VMs that will connect to a remote network over a VPN. (Note that not checking the RDP certificate is no improvement over not checking the WinRM certificate.) #> function Enable-UntrustedOutboundWinRmConnections { [CmdletBinding()] Param() Set-Item WSMan:\localhost\Client\Auth\CredSSP $True Set-Item WSMan:\localhost\Service\Auth\CredSSP $True set-item WSMan:\localhost\Client\TrustedHosts * Restart-Service WinRm } # Exports: #TODO $emmParams = @{ Alias = @("sevenzip") Variable = @("ArchitectureId") Function = "*" # Function = @( # "Get-OSArchitecture" # "Get-LabTempDir" # "Install-SevenZip" # "Install-VBoxAdditions" # ) } export-modulemember @emmParams