diff --git a/scripts/Common/Scripts/Config.ps1 b/scripts/Common/Scripts/Config.ps1 index e6a86173..90e82ae9 100644 --- a/scripts/Common/Scripts/Config.ps1 +++ b/scripts/Common/Scripts/Config.ps1 @@ -1,56 +1,18 @@ using namespace Microsoft.Win32; using namespace System.Management.Automation.Host; -using namespace System.Security.AccessControl; -using namespace System.Security.Principal; - -enum WindowsInstallerStage { - Initialize - Run - Cleanup - Completed -} - -enum SetupStage { - Initialize - Configure - Install - CreateUser -} - -enum BackupStage { - Initialize - Backup - BackupUsers -} - -enum UserStage { - Create - Configure - Cleanup - Completed -} $null = New-Module { - [string] $configRoot = "HKLM:\Software\PortValhalla"; - [string] $stageOption = "Stage"; - [string] $setupStageOption = "SetupStage"; - [string] $backupStageOption = "BackupStage"; - [string] $userOption = "SetupUser"; - [string] $userStageOption = "UserStage"; - [string] $accountOption = "MSAccount"; - [string] $finishedOption = "Finished"; - [RegistryKey] $key = $null; + . "$PSScriptRoot/SoftwareManagement.ps1"; + . "$PSScriptRoot/../../Windows/Scripts/Registry.ps1"; + . "$PSScriptRoot/../../Windows/Types/WindowsInstallerAction.ps1"; <# .SYNOPSIS Prompts the user to select a profile to act on. #> function Show-ProfileNamePrompt { - . "$PSScriptRoot/../../Windows/Types/WindowsInstallerAction.ps1"; $profiles = & { - . "$PSScriptRoot/SoftwareManagement.ps1"; - if (-not $IsWindows -or (Test-Command "wsl")) { return Invoke-ConfigScript "getProfiles"; } else { @@ -93,75 +55,6 @@ $null = New-Module { } } - <# - .SYNOPSIS - Converts the specified path to linux and escapes it for the use in a script. - - .PARAMETER Path - The path to convert. - #> - function ConvertTo-LinuxPath { - param( - [string] $Path - ) - - & { - $ErrorActionPreference = 'Continue'; - $completed = $false; - - while (-not $completed) { - $job = Start-Job { - $env:Value = Resolve-Path $Using:Path; - $env:WSLENV = "Value/p"; - $result = wsl -- bash -c 'echo "$Value"'; - wsl -e printf "%q" "$result"; - }; - - $result = Receive-Job -Wait $job; - - - if ((Split-Path -Leaf $Path) -ne (Split-Path -Leaf $result)) { - Write-Error "The result of the path conversion of ``$Path`` was unexpected: ``$result``"; - continue; - } - - if ($job.State -ne ([System.Management.Automation.JobState]::Completed)) { - Write-Error "An error occurred while converting ``$Path`` to a Linux path.`nOutput: ``$result``"; - continue; - } - - $completed = $true; - } - - $result; - }; - } - - <# - .SYNOPSIS - Gets the registry key containing options related to the setup. - #> - function Get-SetupConfigKey { - if (-not (Test-Path $configRoot)) { - $key = New-Item $configRoot; - $acl = Get-Acl $configRoot; - - $acl.AddAccessRule( - [RegistryAccessRule]::new( - [SecurityIdentifier]::new([WellKnownSidType]::BuiltinUsersSid, $null), - [RegistryRights]::FullControl, - [InheritanceFlags]::ObjectInherit -bor [InheritanceFlags]::ContainerInherit, - [PropagationFlags]::None, - [AccessControlType]::Allow)); - - Set-Acl $configRoot $acl; - } else { - $key = Get-Item $configRoot; - } - - return $key; - } - <# .SYNOPSIS Runs a script based on the `config.fish` script. @@ -174,6 +67,7 @@ $null = New-Module { [string] $Script ) + . "$PSScriptRoot/../../Windows/Scripts/WSL.ps1"; $scriptPath = "$PSScriptRoot/../../Common/Scripts/config.fish"; if ($env:CONFIG_NAME -or ($Script -eq "getProfiles")) { @@ -335,273 +229,4 @@ $null = New-Module { param() Get-OSConfig "setupUser.name"; } - - <# - .SYNOPSIS - Gets the value of an option related to the setup. - - .PARAMETER Name - The name of the option value to get. - #> - function Get-SetupOption { - param( - [string] $Name - ) - - $key = Get-SetupConfigKey; - - if ($key.GetValueNames().Contains($Name)) { - return $key.GetValue($Name); - } else { - return $null; - } - } - - <# - .SYNOPSIS - Sets the value of an option related to the setup. - - .PARAMETER Name - The name of the option to set. - - .PARAMETER Value - The value to set the option to. - #> - function Set-SetupOption { - param( - [string] $Name, - $Value - ) - - $key = (Get-SetupConfigKey).PSPath; - - if ($null -eq $Value) { - Remove-ItemProperty $key -Name $Name; - } else { - $null = Set-ItemProperty $key -Name $Name -Value $Value; - } - } - - <# - .SYNOPSIS - Gets the name of the current stage of the Windows install script action. - #> - function Get-Stage { - $stage = Get-SetupOption $stageOption; - - if ($null -ne $stage) { - $stage = [WindowsInstallerStage]$stage; - } - - return $stage; - } - - <# - .SYNOPSIS - Sets the name of the current stage of the Windows install script action. - - .PARAMETER Name - The name of the stage to set. - #> - function Set-Stage { - param( - $Name - ) - - if (-not (($null -eq $Name) -or ($Name -is [string]))) { - $Name = ([WindowsInstallerStage]$Name).ToString(); - } - - $null = Set-SetupOption $stageOption $Name; - } - - <# - .SYNOPSIS - Gets the name of the current setup stage. - #> - function Get-SetupStage { - $stage = Get-SetupOption $setupStageOption; - - if ($null -ne $stage) { - $stage = [SetupStage]$stage; - } - - return $stage; - } - - <# - .SYNOPSIS - Sets the current stage. - - .PARAMETER Name - The name to set the current stage to. - #> - function Set-SetupStage { - param( - $Name - ) - - if (-not (($null -eq $Name) -or ($Name -is [string]))) { - $Name = ([SetupStage]$Name).ToString(); - } - - $null = Set-SetupOption $setupStageOption $Name; - } - - <# - .SYNOPSIS - Gets the name of the current stage of the backup. - #> - function Get-BackupStage { - $stage = Get-SetupOption $backupStageOption; - - if ($null -ne $stage) { - $stage = [BackupStage]$stage; - } - - return $stage; - } - - <# - .SYNOPSIS - Sets the current stage of the backup. - - .PARAMETER Name - The name to set the current stage to. - #> - function Set-BackupStage { - param( - $Name - ) - - if (-not (($null -eq $Name) -or ($Name -is [string]))) { - $Name = ([BackupStage]$Name).ToString(); - } - - $null = Set-SetupOption $backupStageOption $Name; - } - - <# - .SYNOPSIS - Gets the current user to set up. - #> - function Get-CurrentUser { - return (Get-SetupOption $userOption) ?? 0; - } - - <# - .SYNOPSIS - Sets the index of the current user to set up. - - .PARAMETER Value - The index of the user to set up. - #> - function Set-CurrentUser { - param( - [int] $Value - ) - - Set-SetupOption $userOption $value; - } - - <# - .SYNOPSIS - Gets the name of the current stage of the user setup. - #> - function Get-UserStage { - $stage = Get-SetupOption $userStageOption; - - if ($null -ne $stage) { - $stage = [UserStage]$stage; - } - - return $stage; - } - - <# - .SYNOPSIS - Sets the current stage of the user setup. - - .PARAMETER Name - The name of the stage to set. - #> - function Set-UserStage { - param( - $Name - ) - - if (-not (($null -eq $Name) -or ($Name -is [string]))) { - $Name = ([UserStage]$Name).ToString(); - } - - $null = Set-SetupOption $userStageOption $Name; - } - - <# - .SYNOPSIS - Gets the name of the microsoft account to create. - #> - function Get-MSAccountName { - return Get-SetupOption $accountOption; - } - - <# - .SYNOPSIS - Sets the name of the microsoft account to create. - - .PARAMETER Name - The name of the microsoft account to create. - #> - function Set-MSAccountName { - param( - [string] $Name - ) - - Set-SetupOption $accountOption $Name; - } - - <# - .SYNOPSIS - Gets a value indicating whether the setup has finished. - #> - function Get-IsFinished { - return [bool](Get-SetupOption $finishedOption); - } - - <# - .SYNOPSIS - Sets a value indicating whether the setup has finished. - #> - function Set-IsFinished { - param( - $Value - ) - - Set-SetupOption $finishedOption $true; - } - - <# - .SYNOPSIS - Checks whether the running system is a QEMU virtual machine. - #> - function Test-Qemu { - ((Get-WmiObject win32_computersystem).Manufacturer) -eq "QEMU"; - } - - <# - .SYNOPSIS - Checks whether the current user is the setup user. - #> - function Test-SetupUser { - ($IsWindows ? $env:UserName : $env:USER) -eq (Get-SetupUser); - } - - <# - .SYNOPSIS - Checks whether the active session is executed with admin rights. - #> - function Test-Admin { - net session 2> $null | Out-Null; - return $?; - } } diff --git a/scripts/Common/Scripts/Operations.ps1 b/scripts/Common/Scripts/Operations.ps1 index 3abee6e4..911e48bd 100644 --- a/scripts/Common/Scripts/Operations.ps1 +++ b/scripts/Common/Scripts/Operations.ps1 @@ -1,35 +1,17 @@ using namespace System.Management.Automation.Host; -. "$PSScriptRoot/../Types/OneShotTask.ps1"; - $null = New-Module { - . "$PSScriptRoot/Config.ps1"; . "$PSScriptRoot/Scripting.ps1"; - . "$PSScriptRoot/../Scripts/SoftwareManagement.ps1"; - . "$PSScriptRoot/../Types/OneShotTask.ps1"; + . "$PSScriptRoot/SoftwareManagement.ps1"; . "$PSScriptRoot/../../Windows/Scripts/Constants.ps1"; . "$PSScriptRoot/../../Windows/Scripts/Hooks.ps1"; . "$PSScriptRoot/../../Windows/Scripts/PowerManagement.ps1"; . "$PSScriptRoot/../../Windows/Scripts/Registry.ps1"; . "$PSScriptRoot/../../Windows/Scripts/Security.ps1"; + . "$PSScriptRoot/../../Windows/Scripts/SoftwareManagement.ps1"; + . "$PSScriptRoot/../../Windows/Scripts/System.ps1"; + . "$PSScriptRoot/../../Windows/Scripts/Tasks.ps1"; . "$PSScriptRoot/../../Windows/Scripts/WSL.ps1"; - $oneShotTaskName = "PortValhalla OneShot"; - $logName = "Application"; - $oneShotTrigger = 1337; - $taskOption = "OneShotTask"; - - $getErrorPath = { - Join-Path (Get-ArtifactRoot) "error.txt"; - }; - - $getUserName = { - "$(Get-SetupUser)OneShot"; - }; - - $taskSetter = { - param([Nullable[OneShotTask]] $Task) - Set-SetupOption $taskOption ([string]$Task); - }; <# .SYNOPSIS @@ -289,141 +271,6 @@ $null = New-Module { & $cleanup; } - <# - .SYNOPSIS - Gets the current OneShot task. - #> - function Get-OneShotTask { - $task = Get-SetupOption $taskOption; - - if ($task) { - return [OneShotTask]$task; - } else { - return $null; - } - } - - <# - .SYNOPSIS - Registers a task for listening to OneShot invocations. - #> - function Enable-OneShotListener { - $tempTask = "PortValhalla Temp"; - $user = & $getUserName; - $password = [string]([guid]::NewGuid()); - - $adminGroup = @{ - SID = [SecurityIdentifier]::new([WellKnownSidType]::BuiltinAdministratorsSid, $null); - }; - - $null = New-LocalUser -Name $user -Password (ConvertTo-SecureString -AsPlainText $password); - Add-LocalGroupMember -Member $user @adminGroup; - $logPath = Join-Path (Get-ArtifactRoot) "OneShotTask.log"; - $path = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"; - $null = New-Item -Force -ErrorAction SilentlyContinue $path; - Set-ItemProperty $path -Name $user -Value 0; - - $action = New-ScheduledTaskAction -Execute "pwsh" -Argument "-Command & { $([string](Get-StartupCommand)) } 2>&1 | Tee-Object -FilePath $(ConvertTo-Injection $logPath)"; - schtasks /Create /SC ONEVENT /EC $logName /MO "*[System[Provider[@Name='$logName'] and EventID=$($oneShotTrigger)]]" /TR cmd.exe /TN $tempTask; - $trigger = (Get-ScheduledTask $tempTask).Triggers; - $null = Register-ScheduledTask -Force $oneShotTaskName -Action $action -Trigger $trigger -RunLevel Highest -User $user -Password $password; - $null = Unregister-ScheduledTask -Confirm:$false $tempTask; - } - - <# - .SYNOPSIS - Removes the OneShot task. - #> - function Disable-OneShotListener { - Unregister-ScheduledTask -Confirm:$false $oneShotTaskName; - $user = Get-LocalUser (& $getUserName); - [string] $sid = $user.SID; - Remove-LocalUser $user; - Get-CimInstance Win32_UserProfile | Where-Object { $_.SID -eq $sid } | Remove-CimInstance; - } - - <# - .SYNOPSIS - Invokes a one-shot task. - - .PARAMETER Task - The task to run. - #> - function Invoke-OneShot { - param( - [OneShotTask] $Task - ) - - $errorPath = & $getErrorPath; - & $taskSetter $Task; - - & { - $identifier = "EventLog$oneShotTrigger"; - $log = [System.Diagnostics.EventLog]::new($logName); - $log.EnableRaisingEvents = $true; - - $null = Register-ObjectEvent -InputObject $log -EventName EntryWritten -Action { - $entry = $Event.SourceEventArgs.Entry; - $trigger = $Event.MessageData.Trigger; - $identifier = $Event.MessageData.Identifier; - - if ($entry.EventID -eq $trigger) { - $null = New-Event -SourceIdentifier $identifier; - } - } ` - -MessageData @{ - Trigger = $oneShotTrigger; - Identifier = $identifier; - }; - - Write-EventLog -LogName $logName -Source $logName -EventId $oneShotTrigger -Message "Starting OneShot task ``$(Get-OneShotTask)``…"; - - for ($i = 0; $i -lt 2; $i++) { - Remove-Event -EventIdentifier (Wait-Event -SourceIdentifier $identifier).EventIdentifier; - } - }; - - if (Test-Path $errorPath) { - $errorMessage = Get-Content $errorPath; - Remove-Item $errorPath; - - if ($errorMessage) { - Write-Error $errorMessage; - } - } - } - - # ToDo: Store Run-OneShot and Receive-OneShot somewhere else in Windows folder - - <# - .SYNOPSIS - Executes the specified action and notifies the OneShot task executor. - #> - function Start-OneShot { - try { - Write-Host "Running OneShot task ``$(Get-OneShotTask)``"; - - switch (Get-OneShotTask) { - ([OneShotTask]::InitializeMSAccount) { - Initialize-UserCreation; - } - ([OneShotTask]::DisableUAC) { - Disable-UAC; - Register-Setup; - } - } - } - catch { - $errorPath = & $getErrorPath; - Set-Content -Path $errorPath -Value $Error; - Set-UserPermissions $errorPath; - } - finally { - & $taskSetter $null; - Write-EventLog -LogName $logName -Source $logName -EventId $oneShotTrigger -Message "The OneShot task ``$(Get-OneShotTask)`` finished."; - } - } - <# .SYNOPSIS Clears resources allocated during the operation. diff --git a/scripts/Common/Scripts/Software.ps1 b/scripts/Common/Scripts/Software.ps1 index f8e68bfb..4731f8c3 100644 --- a/scripts/Common/Scripts/Software.ps1 +++ b/scripts/Common/Scripts/Software.ps1 @@ -1,297 +1,11 @@ -. "$PSScriptRoot/Config.ps1"; . "$PSScriptRoot/Operations.ps1"; . "$PSScriptRoot/System.ps1"; . "$PSScriptRoot/../Types/InstallerAction.ps1"; $null = New-Module { - . "$PSScriptRoot/BrowserAutomation.ps1"; - . "$PSScriptRoot/SoftwareManagement.ps1"; . "$PSScriptRoot/../Types/InstallerAction.ps1"; $userArgument = "name"; - $chocoRunner = { - param( - [string] $Action = 'install', - [string[]] $ArgumentList, - [scriptblock] $Guard = { $true }, - [Parameter(Position = 0)] - [string] $Name, - [Parameter(ValueFromRemainingArguments = $true)] - [string[]] $AdditionalNames = @() - ) - - [System.Collections.ArrayList] $Names = @(); - $null = $Names.Add($Name); - $Names.AddRange($AdditionalNames); - - if (-not ($Force.IsPresent)) { - for ($i = $Names.Count - 1; $i -ge 0; $i--) { - $name = $Names[$i]; - - if (-not (& $Guard $name)) { - $Names.RemoveAt($i); - } - } - } - - if ($Names.Count -ge 1) { - choco $Action -y @ArgumentList @Names; - } - }; - - $wingetRunner = { - param( - [string] $Action = 'install', - [string[]] $ArgumentList, - [scriptblock] $Guard = { $true }, - [Parameter(Position = 0)] - [string] $Name, - [Parameter(ValueFromRemainingArguments = $true)] - [string[]] $AdditionalNames = @() - ) - - [System.Collections.ArrayList] $Names = @(); - $null = $Names.Add($Name); - $Names.AddRange($AdditionalNames); - - [string[]] $arguments = $ArgumentList + (& { - if ($Action -eq 'install') { - @("--accept-package-agreements") - }; - }); - - foreach ($name in $Names) { - if ($Force.IsPresent -or (& $Guard $name $PSBoundParameters)) { - winget $Action ` - --accept-source-agreements ` - --source winget ` - @arguments ` - --exact --id $name ; - } else { - Write-Host "Package ``$name`` is already installed" - } - } - }; - - <# - .SYNOPSIS - Installs the specified packages using chocolatey. - - .PARAMETER Names - The names of the packages to install. - #> - function Install-ChocoPackage { - param( - [switch] $Force, - [string[]] $ArgumentList, - [Parameter(Position=0)] - [string] $Name, - [Parameter(ValueFromRemainingArguments = $true)] - [string[]] $AdditionalNames = @() - ) - - & $chocoRunner @PSBoundParameters -Guard { - param($Name) - if (Test-ChocoPackage $Name) { - Write-Host "Package ``$Name`` is already installed" - $false; - } else { - $true; - } - }; - } - - <# - .SYNOPSIS - Uninstalls the specified packages using chocolatey. - #> - function Uninstall-ChocoPackage { - param( - [string[]] $ArgumentList, - [Parameter(Position=0)] - [string] $Name, - [Parameter(ValueFromRemainingArguments = $true)] - [string[]] $AdditionalNames = @() - ) - - & $chocoRunner @PSBoundParameters -Action 'uninstall' -Guard { - param($Name) - if (Test-ChocoPackage $Name) { - $true; - } else { - Write-Host "Package ``$Name`` is not installed"; - $false; - } - }; - } - - <# - .SYNOPSIS - Installs the specified packages using `winget`. - - .PARAMETER Names - The names of the packages to install. - #> - function Install-WingetPackage { - param( - [switch] $Force, - [string[]] $ArgumentList, - [Parameter(Position=0)] - [string] $Name, - [Parameter(ValueFromRemainingArguments = $true)] - [string[]] $AdditionalNames = @() - ) - - & $wingetRunner @PSBoundParameters ` - -Guard { - param($Name, $Parameters) - if (Test-WingetPackage -Name $Name @Parameters) { - Write-Host "Package ``$Name`` is already installed" - $false; - } else { - $true; - } - }; - } - - <# - .SYNOPSIS - Uninstalls the specified packages using `winget`. - #> - function Uninstall-WingetPackage { - param( - [string[]] $ArgumentList, - [Parameter(Position=0)] - [string] $Name, - [Parameter(ValueFromRemainingArguments = $true)] - [string[]] $AdditionalNames = @() - ) - - & $wingetRunner @PSBoundParameters -Action 'uninstall' -Guard { - param($Name, $Parameters) - if (Test-WingetPackage -Name $Name @Parameters) { - $true; - } else { - Write-Host "Package ``$Name`` is not installed" - $false; - } - }; - } - - <# - .SYNOPSIS - Installs a setup package from the specified source. - - .PARAMETER Source - The location of the setup package. - - .PARAMETER ArgumentList - The arguments to pass to the setup package. - - .PARAMETER Local - A value indicating whether the setup package is stored locally. - #> - function Install-SetupPackage { - param( - [string] $Source, - [string[]] $ArgumentList = @("/S"), - [switch] $Local - ) - - [string] $dir = $null; - [string] $filePath = $null; - - if (-not ($Local.IsPresent)) { - $dir = New-TemporaryDirectory; - Write-Host "Determining the file name of ``$Source``…"; - $fileName = ([uri]$Source).Segments[-1]; - Write-Host "Found name ``$fileName``"; - $filePath = Join-Path $dir $fileName; - - Write-Host "Downloading setup file from ``$Source``"; - Invoke-WebRequest $Source -OutFile $filePath; - } else { - $filePath = $Source; - } - - Write-Host "Starting installation of ``$(Split-Path -Leaf $filePath)``"; - Start-Process -Wait -WorkingDirectory (Split-Path -Parent $filePath) -FilePath $filePath -ArgumentList $ArgumentList; - - if ($dir) { - Remove-Item -Recurse $dir; - } - } - - <# - .SYNOPSIS - Installs a package downloaded from ASUS. - - .PARAMETER URL - The URL to download the package from. - #> - function Install-AsusPackage { - param( - [string] $URL - ) - - $file = "AsusPackage.zip"; - $dir = New-TemporaryDirectory; - $unpackDir = New-TemporaryDirectory; - - $null = Push-Location $dir; - Invoke-WebRequest $URL -OutFile $file; - Expand-Archive $file $unpackDir; - $null = Pop-Location; - Remove-Item -Recurse $dir; - - $null = Start-Process -Wait -WorkingDirectory $unpackDir -FilePath (Join-Path $unpackDir "AsusSetup.exe") -ArgumentList "/S"; - Remove-Item -Recurse $unpackDir; - } - - <# - .SYNOPSIS - Downloads and installs a package from the AMD website. - - .PARAMETER URL - The URL to download the package from. - #> - function Install-AmdPackage { - param( - [string] $URL - ) - - $dir = New-TemporaryDirectory; - $cookieBannerSelector = "#onetrust-consent-sdk"; - $osSelector = "div[id$='oscategory']>div[id$='-0']"; - $downloadSelector = "$osSelector div[id$='panel'] .button a"; - - $file = Start-CustomBrowserDownload @PSBoundParameters -OutDir $dir -Action { - param( - [OpenQA.Selenium.Firefox.FirefoxDriver] $Browser - ) - - $osContainer = $Browser.FindElement([OpenQA.Selenium.By]::CssSelector($osSelector)); - - if (-not ([bool] $osContainer.GetAttribute("cmp-expanded"))) { - $osContainer.Click(); - } - - $download = { - $browser.FindElement([OpenQA.Selenium.By]::CssSelector($downloadSelector)).Click(); - }; - - try { - & $download; - } catch { - $null = $Browser.ExecuteScript("document.querySelector('$cookieBannerSelector').remove()"); - & $download; - } - }; - - Start-Process -Wait -WorkingDirectory (Split-Path -Parent "$file") -FilePath "$file" -ArgumentList "/S"; - Remove-Item -Recurse $dir; - } - function Start-SoftwareInstaller { param( [string] $Name, diff --git a/scripts/Common/Scripts/SoftwareManagement.ps1 b/scripts/Common/Scripts/SoftwareManagement.ps1 index 3689438a..8f53f7d7 100644 --- a/scripts/Common/Scripts/SoftwareManagement.ps1 +++ b/scripts/Common/Scripts/SoftwareManagement.ps1 @@ -1,36 +1,3 @@ -<# - .SYNOPSIS - Checks whether the specified package has been installed using Chocolatey. - - .PARAMETER Name - The name of the package to check. -#> -function Test-ChocoPackage { - [OutputType([bool])] - param( - [string] $Name - ); - - -not [string]::IsNullOrEmpty((choco list --limit-output --exact $name)); -} - -<# - .SYNOPSIS - Checks whether a `winget` package with the specified id is installed. - - .PARAMETER ID - The id of the package to check. -#> -function Test-WingetPackage { - [OutputType([bool])] - param( - [string] $Name, - [string[]] $ArgumentList - ) - - & { $null = winget list --accept-source-agreements -e --id $Name @ArgumentList; $?; }; -} - <# .SYNOPSIS Checks whether a command with the specified name exists. @@ -46,21 +13,6 @@ function Test-Command { [bool] (Get-Command $Name -ErrorAction SilentlyContinue); } -<# - .SYNOPSIS - Checks whether `winget` is working properly. -#> -function Test-Winget { - (Test-Command winget) -and ( - & { - $output = winget source update winget; - - $? -and -not ([System.Linq.Enumerable]::Any( - [string[]]($output), - [System.Func[string,bool]]{ param($line) $line -eq "Cancelled"; })); - }); -} - <# .SYNOPSIS Checks whether a package with the specified name is installed. diff --git a/scripts/Common/Scripts/System.ps1 b/scripts/Common/Scripts/System.ps1 index 2fa7db88..9dca7e8c 100644 --- a/scripts/Common/Scripts/System.ps1 +++ b/scripts/Common/Scripts/System.ps1 @@ -9,60 +9,8 @@ function New-TemporaryDirectory { <# .SYNOPSIS - Removes desktop icons which apply to the specified pattern. - - .PARAMETER Pattern - The pattern to match the icons to delete. + Checks whether the current user is the setup user. #> -function Remove-DesktopIcon { - param( - [string] $Pattern - ) - - $path = "Desktop/$Pattern"; - - foreach ($userDir in @("~", $env:PUBLIC, "$env:SystemDrive/Users/Default")) { - $fullName = "$userDir/$path"; - - if (Test-Path -PathType Leaf $fullName) { - Remove-Item $fullName; - } - } -} - -<# - .SYNOPSIS - Adds a new shortcut to the start menu. - - .PARAMETER Name - The name of the icon to create. - - .PARAMETER Target - The file to link to. -#> -function Add-StartMenuIcon { - param( - [string] $Name, - [string] $Target - ) - - Import-Module KnownFolders; - Import-Module "$env:ChocolateyInstall/helpers/chocolateyInstaller.psm1"; - Install-ChocolateyShortcut -ShortcutFilePath "$((Get-KnownFolder "Common Programs").Path)/$Name.lnk" -TargetPath ((Get-Item $Target).FullName); -} - -<# - .SYNOPSIS - Removes icons from the task bar. - - .PARAMETER Pattern - The pattern of the icon names to remove. -#> -function Remove-TaskbarItem { - param( - [string] $Pattern - ) - - Import-Module -UseWindowsPowerShell PinnedItem; - Get-PinnedItem -Type TaskBar | Where-Object { $_.Name -like "$Pattern" } | ForEach-Object { Remove-PinnedItem $_ }; +function Test-SetupUser { + ($IsWindows ? $env:UserName : $env:USER) -eq (Get-SetupUser); } diff --git a/scripts/Windows/Drivers/Tobii EyeX/Manage.ps1 b/scripts/Windows/Drivers/Tobii EyeX/Manage.ps1 index 8755f3a9..c812bdf5 100644 --- a/scripts/Windows/Drivers/Tobii EyeX/Manage.ps1 +++ b/scripts/Windows/Drivers/Tobii EyeX/Manage.ps1 @@ -6,6 +6,7 @@ param( & { param($parameters); + . "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; $softwarePath = "$PSScriptRoot/../../Software"; diff --git a/scripts/Windows/OS/Manage.ps1 b/scripts/Windows/OS/Manage.ps1 index 5d715cb4..6c86573f 100644 --- a/scripts/Windows/OS/Manage.ps1 +++ b/scripts/Windows/OS/Manage.ps1 @@ -10,13 +10,14 @@ $null = New-Module { . "$PSScriptRoot/../Scripts/PowerManagement.ps1"; . "$PSScriptRoot/../Scripts/Registry.ps1"; . "$PSScriptRoot/../Scripts/Security.ps1"; + . "$PSScriptRoot/../Scripts/SoftwareManagement.ps1"; + . "$PSScriptRoot/../Scripts/System.ps1"; . "$PSScriptRoot/../Scripts/Update.ps1"; . "$PSScriptRoot/../Scripts/Users.ps1"; . "$PSScriptRoot/../Types/WindowsInstallerAction.ps1"; . "$PSScriptRoot/../../Common/Scripts/Config.ps1"; . "$PSScriptRoot/../../Common/Scripts/Operations.ps1"; . "$PSScriptRoot/../../Common/Scripts/Scripting.ps1"; - . "$PSScriptRoot/../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../Common/Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../Common/Types/InstallerAction.ps1"; diff --git a/scripts/Windows/Scripts/Deployment.ps1 b/scripts/Windows/Scripts/Deployment.ps1 index fb8655c4..dab15880 100644 --- a/scripts/Windows/Scripts/Deployment.ps1 +++ b/scripts/Windows/Scripts/Deployment.ps1 @@ -1,5 +1,5 @@ . "$PSScriptRoot/PowerManagement.ps1"; -. "$PSScriptRoot/../../Common/Scripts/Software.ps1"; +. "$PSScriptRoot/SoftwareManagement.ps1"; . "$PSScriptRoot/../../Common/Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../Common/Types/InstallerAction.ps1"; diff --git a/scripts/Windows/Scripts/PowerManagement.ps1 b/scripts/Windows/Scripts/PowerManagement.ps1 index 45e003a3..e83e2933 100644 --- a/scripts/Windows/Scripts/PowerManagement.ps1 +++ b/scripts/Windows/Scripts/PowerManagement.ps1 @@ -2,7 +2,6 @@ using namespace Microsoft.Win32; $null = New-Module { . "$PSScriptRoot/../Scripts/Registry.ps1"; - . "$PSScriptRoot/../../Common/Scripts/Config.ps1"; . "$PSScriptRoot/../../Common/Scripts/Scripting.ps1"; [RegistryKey] $key = $null; $runOncePath = "Software\Microsoft\Windows\CurrentVersion\RunOnce"; diff --git a/scripts/Windows/Scripts/Registry.ps1 b/scripts/Windows/Scripts/Registry.ps1 index 2ce9b9e6..1e4adbd7 100644 --- a/scripts/Windows/Scripts/Registry.ps1 +++ b/scripts/Windows/Scripts/Registry.ps1 @@ -1,6 +1,45 @@ using namespace Microsoft.Win32; +using namespace System.Security.AccessControl; +using namespace System.Security.Principal; + +enum WindowsInstallerStage { + Initialize + Run + Cleanup + Completed +} + +enum SetupStage { + Initialize + Configure + Install + CreateUser +} + +enum BackupStage { + Initialize + Backup + BackupUsers +} + +enum UserStage { + Create + Configure + Cleanup + Completed +} $null = New-Module { + [string] $configRoot = "HKLM:\Software\PortValhalla"; + [string] $stageOption = "Stage"; + [string] $setupStageOption = "SetupStage"; + [string] $backupStageOption = "BackupStage"; + [string] $userOption = "SetupUser"; + [string] $userStageOption = "UserStage"; + [string] $accountOption = "MSAccount"; + [string] $finishedOption = "Finished"; + [RegistryKey] $key = $null; + function Edit-DefaultUserKey { param( [scriptblock] $Action @@ -17,6 +56,31 @@ $null = New-Module { & reg unload $regRootPath; } + <# + .SYNOPSIS + Gets the registry key containing options related to the setup. + #> + function Get-SetupConfigKey { + if (-not (Test-Path $configRoot)) { + $key = New-Item $configRoot; + $acl = Get-Acl $configRoot; + + $acl.AddAccessRule( + [RegistryAccessRule]::new( + [SecurityIdentifier]::new([WellKnownSidType]::BuiltinUsersSid, $null), + [RegistryRights]::FullControl, + [InheritanceFlags]::ObjectInherit -bor [InheritanceFlags]::ContainerInherit, + [PropagationFlags]::None, + [AccessControlType]::Allow)); + + Set-Acl $configRoot $acl; + } else { + $key = Get-Item $configRoot; + } + + return $key; + } + <# .SYNOPSIS Sets a message to show on the login screen. @@ -53,4 +117,248 @@ $null = New-Module { function Disable-BootMessage { Set-BootMessage; } + + <# + .SYNOPSIS + Gets the value of an option related to the setup. + + .PARAMETER Name + The name of the option value to get. + #> + function Get-SetupOption { + param( + [string] $Name + ) + + $key = Get-SetupConfigKey; + + if ($key.GetValueNames().Contains($Name)) { + return $key.GetValue($Name); + } else { + return $null; + } + } + + <# + .SYNOPSIS + Sets the value of an option related to the setup. + + .PARAMETER Name + The name of the option to set. + + .PARAMETER Value + The value to set the option to. + #> + function Set-SetupOption { + param( + [string] $Name, + $Value + ) + + $key = (Get-SetupConfigKey).PSPath; + + if ($null -eq $Value) { + Remove-ItemProperty $key -Name $Name; + } else { + $null = Set-ItemProperty $key -Name $Name -Value $Value; + } + } + + <# + .SYNOPSIS + Gets the name of the current stage of the Windows install script action. + #> + function Get-Stage { + $stage = Get-SetupOption $stageOption; + + if ($null -ne $stage) { + $stage = [WindowsInstallerStage]$stage; + } + + return $stage; + } + + <# + .SYNOPSIS + Sets the name of the current stage of the Windows install script action. + + .PARAMETER Name + The name of the stage to set. + #> + function Set-Stage { + param( + $Name + ) + + if (-not (($null -eq $Name) -or ($Name -is [string]))) { + $Name = ([WindowsInstallerStage]$Name).ToString(); + } + + $null = Set-SetupOption $stageOption $Name; + } + + <# + .SYNOPSIS + Gets the name of the current setup stage. + #> + function Get-SetupStage { + $stage = Get-SetupOption $setupStageOption; + + if ($null -ne $stage) { + $stage = [SetupStage]$stage; + } + + return $stage; + } + + <# + .SYNOPSIS + Sets the current stage. + + .PARAMETER Name + The name to set the current stage to. + #> + function Set-SetupStage { + param( + $Name + ) + + if (-not (($null -eq $Name) -or ($Name -is [string]))) { + $Name = ([SetupStage]$Name).ToString(); + } + + $null = Set-SetupOption $setupStageOption $Name; + } + + <# + .SYNOPSIS + Gets the name of the current stage of the backup. + #> + function Get-BackupStage { + $stage = Get-SetupOption $backupStageOption; + + if ($null -ne $stage) { + $stage = [BackupStage]$stage; + } + + return $stage; + } + + <# + .SYNOPSIS + Sets the current stage of the backup. + + .PARAMETER Name + The name to set the current stage to. + #> + function Set-BackupStage { + param( + $Name + ) + + if (-not (($null -eq $Name) -or ($Name -is [string]))) { + $Name = ([BackupStage]$Name).ToString(); + } + + $null = Set-SetupOption $backupStageOption $Name; + } + + <# + .SYNOPSIS + Gets the current user to set up. + #> + function Get-CurrentUser { + return (Get-SetupOption $userOption) ?? 0; + } + + <# + .SYNOPSIS + Sets the index of the current user to set up. + + .PARAMETER Value + The index of the user to set up. + #> + function Set-CurrentUser { + param( + [int] $Value + ) + + Set-SetupOption $userOption $value; + } + + <# + .SYNOPSIS + Gets the name of the current stage of the user setup. + #> + function Get-UserStage { + $stage = Get-SetupOption $userStageOption; + + if ($null -ne $stage) { + $stage = [UserStage]$stage; + } + + return $stage; + } + + <# + .SYNOPSIS + Sets the current stage of the user setup. + + .PARAMETER Name + The name of the stage to set. + #> + function Set-UserStage { + param( + $Name + ) + + if (-not (($null -eq $Name) -or ($Name -is [string]))) { + $Name = ([UserStage]$Name).ToString(); + } + + $null = Set-SetupOption $userStageOption $Name; + } + + <# + .SYNOPSIS + Gets the name of the microsoft account to create. + #> + function Get-MSAccountName { + return Get-SetupOption $accountOption; + } + + <# + .SYNOPSIS + Sets the name of the microsoft account to create. + + .PARAMETER Name + The name of the microsoft account to create. + #> + function Set-MSAccountName { + param( + [string] $Name + ) + + Set-SetupOption $accountOption $Name; + } + + <# + .SYNOPSIS + Gets a value indicating whether the setup has finished. + #> + function Get-IsFinished { + return [bool](Get-SetupOption $finishedOption); + } + + <# + .SYNOPSIS + Sets a value indicating whether the setup has finished. + #> + function Set-IsFinished { + param( + $Value + ) + + Set-SetupOption $finishedOption $true; + } } diff --git a/scripts/Windows/Scripts/SoftwareManagement.ps1 b/scripts/Windows/Scripts/SoftwareManagement.ps1 new file mode 100644 index 00000000..f86f86dc --- /dev/null +++ b/scripts/Windows/Scripts/SoftwareManagement.ps1 @@ -0,0 +1,335 @@ +$null = New-Module { + . "$PSScriptRoot/../../Common/Scripts/BrowserAutomation.ps1"; + . "$PSScriptRoot/../../Common/Scripts/SoftwareManagement.ps1"; + + $chocoRunner = { + param( + [string] $Action = 'install', + [string[]] $ArgumentList, + [scriptblock] $Guard = { $true }, + [Parameter(Position = 0)] + [string] $Name, + [Parameter(ValueFromRemainingArguments = $true)] + [string[]] $AdditionalNames = @() + ) + + [System.Collections.ArrayList] $Names = @(); + $null = $Names.Add($Name); + $Names.AddRange($AdditionalNames); + + if (-not ($Force.IsPresent)) { + for ($i = $Names.Count - 1; $i -ge 0; $i--) { + $name = $Names[$i]; + + if (-not (& $Guard $name)) { + $Names.RemoveAt($i); + } + } + } + + if ($Names.Count -ge 1) { + choco $Action -y @ArgumentList @Names; + } + }; + + $wingetRunner = { + param( + [string] $Action = 'install', + [string[]] $ArgumentList, + [scriptblock] $Guard = { $true }, + [Parameter(Position = 0)] + [string] $Name, + [Parameter(ValueFromRemainingArguments = $true)] + [string[]] $AdditionalNames = @() + ) + + [System.Collections.ArrayList] $Names = @(); + $null = $Names.Add($Name); + $Names.AddRange($AdditionalNames); + + [string[]] $arguments = $ArgumentList + (& { + if ($Action -eq 'install') { + @("--accept-package-agreements") + }; + }); + + foreach ($name in $Names) { + if ($Force.IsPresent -or (& $Guard $name $PSBoundParameters)) { + winget $Action ` + --accept-source-agreements ` + --source winget ` + @arguments ` + --exact --id $name ; + } else { + Write-Host "Package ``$name`` is already installed" + } + } + }; + + <# + .SYNOPSIS + Checks whether `winget` is working properly. + #> + function Test-Winget { + (Test-Command winget) -and ( + & { + $output = winget source update winget; + + $? -and -not ([System.Linq.Enumerable]::Any( + [string[]]($output), + [System.Func[string,bool]]{ param($line) $line -eq "Cancelled"; })); + }); + } + + <# + .SYNOPSIS + Checks whether the specified package has been installed using Chocolatey. + + .PARAMETER Name + The name of the package to check. + #> + function Test-ChocoPackage { + [OutputType([bool])] + param( + [string] $Name + ); + + -not [string]::IsNullOrEmpty((choco list --limit-output --exact $name)); + } + + <# + .SYNOPSIS + Checks whether a `winget` package with the specified id is installed. + + .PARAMETER ID + The id of the package to check. + #> + function Test-WingetPackage { + [OutputType([bool])] + param( + [string] $Name, + [string[]] $ArgumentList + ) + + & { $null = winget list --accept-source-agreements -e --id $Name @ArgumentList; $?; }; + } + + <# + .SYNOPSIS + Installs the specified packages using chocolatey. + + .PARAMETER Names + The names of the packages to install. + #> + function Install-ChocoPackage { + param( + [switch] $Force, + [string[]] $ArgumentList, + [Parameter(Position=0)] + [string] $Name, + [Parameter(ValueFromRemainingArguments = $true)] + [string[]] $AdditionalNames = @() + ) + + & $chocoRunner @PSBoundParameters -Guard { + param($Name) + if (Test-ChocoPackage $Name) { + Write-Host "Package ``$Name`` is already installed" + $false; + } else { + $true; + } + }; + } + + <# + .SYNOPSIS + Uninstalls the specified packages using chocolatey. + #> + function Uninstall-ChocoPackage { + param( + [string[]] $ArgumentList, + [Parameter(Position=0)] + [string] $Name, + [Parameter(ValueFromRemainingArguments = $true)] + [string[]] $AdditionalNames = @() + ) + + & $chocoRunner @PSBoundParameters -Action 'uninstall' -Guard { + param($Name) + if (Test-ChocoPackage $Name) { + $true; + } else { + Write-Host "Package ``$Name`` is not installed"; + $false; + } + }; + } + + <# + .SYNOPSIS + Installs the specified packages using `winget`. + + .PARAMETER Names + The names of the packages to install. + #> + function Install-WingetPackage { + param( + [switch] $Force, + [string[]] $ArgumentList, + [Parameter(Position=0)] + [string] $Name, + [Parameter(ValueFromRemainingArguments = $true)] + [string[]] $AdditionalNames = @() + ) + + & $wingetRunner @PSBoundParameters ` + -Guard { + param($Name, $Parameters) + if (Test-WingetPackage -Name $Name @Parameters) { + Write-Host "Package ``$Name`` is already installed" + $false; + } else { + $true; + } + }; + } + + <# + .SYNOPSIS + Uninstalls the specified packages using `winget`. + #> + function Uninstall-WingetPackage { + param( + [string[]] $ArgumentList, + [Parameter(Position=0)] + [string] $Name, + [Parameter(ValueFromRemainingArguments = $true)] + [string[]] $AdditionalNames = @() + ) + + & $wingetRunner @PSBoundParameters -Action 'uninstall' -Guard { + param($Name, $Parameters) + if (Test-WingetPackage -Name $Name @Parameters) { + $true; + } else { + Write-Host "Package ``$Name`` is not installed" + $false; + } + }; + } + + <# + .SYNOPSIS + Installs a setup package from the specified source. + + .PARAMETER Source + The location of the setup package. + + .PARAMETER ArgumentList + The arguments to pass to the setup package. + + .PARAMETER Local + A value indicating whether the setup package is stored locally. + #> + function Install-SetupPackage { + param( + [string] $Source, + [string[]] $ArgumentList = @("/S"), + [switch] $Local + ) + + [string] $dir = $null; + [string] $filePath = $null; + + if (-not ($Local.IsPresent)) { + $dir = New-TemporaryDirectory; + Write-Host "Determining the file name of ``$Source``…"; + $fileName = ([uri]$Source).Segments[-1]; + Write-Host "Found name ``$fileName``"; + $filePath = Join-Path $dir $fileName; + + Write-Host "Downloading setup file from ``$Source``"; + Invoke-WebRequest $Source -OutFile $filePath; + } else { + $filePath = $Source; + } + + Write-Host "Starting installation of ``$(Split-Path -Leaf $filePath)``"; + Start-Process -Wait -WorkingDirectory (Split-Path -Parent $filePath) -FilePath $filePath -ArgumentList $ArgumentList; + + if ($dir) { + Remove-Item -Recurse $dir; + } + } + + <# + .SYNOPSIS + Installs a package downloaded from ASUS. + + .PARAMETER URL + The URL to download the package from. + #> + function Install-AsusPackage { + param( + [string] $URL + ) + + $file = "AsusPackage.zip"; + $dir = New-TemporaryDirectory; + $unpackDir = New-TemporaryDirectory; + + $null = Push-Location $dir; + Invoke-WebRequest $URL -OutFile $file; + Expand-Archive $file $unpackDir; + $null = Pop-Location; + Remove-Item -Recurse $dir; + + $null = Start-Process -Wait -WorkingDirectory $unpackDir -FilePath (Join-Path $unpackDir "AsusSetup.exe") -ArgumentList "/S"; + Remove-Item -Recurse $unpackDir; + } + + <# + .SYNOPSIS + Downloads and installs a package from the AMD website. + + .PARAMETER URL + The URL to download the package from. + #> + function Install-AmdPackage { + param( + [string] $URL + ) + + $dir = New-TemporaryDirectory; + $cookieBannerSelector = "#onetrust-consent-sdk"; + $osSelector = "div[id$='oscategory']>div[id$='-0']"; + $downloadSelector = "$osSelector div[id$='panel'] .button a"; + + $file = Start-CustomBrowserDownload @PSBoundParameters -OutDir $dir -Action { + param( + [OpenQA.Selenium.Firefox.FirefoxDriver] $Browser + ) + + $osContainer = $Browser.FindElement([OpenQA.Selenium.By]::CssSelector($osSelector)); + + if (-not ([bool] $osContainer.GetAttribute("cmp-expanded"))) { + $osContainer.Click(); + } + + $download = { + $browser.FindElement([OpenQA.Selenium.By]::CssSelector($downloadSelector)).Click(); + }; + + try { + & $download; + } catch { + $null = $Browser.ExecuteScript("document.querySelector('$cookieBannerSelector').remove()"); + & $download; + } + }; + + Start-Process -Wait -WorkingDirectory (Split-Path -Parent "$file") -FilePath "$file" -ArgumentList "/S"; + Remove-Item -Recurse $dir; + } +}; diff --git a/scripts/Windows/Scripts/System.ps1 b/scripts/Windows/Scripts/System.ps1 new file mode 100644 index 00000000..5bbf99b6 --- /dev/null +++ b/scripts/Windows/Scripts/System.ps1 @@ -0,0 +1,76 @@ +<# + .SYNOPSIS + Checks whether the running system is a QEMU virtual machine. +#> +function Test-Qemu { + ((Get-WmiObject win32_computersystem).Manufacturer) -eq "QEMU"; +} + +<# + .SYNOPSIS + Checks whether the active session is executed with admin rights. +#> +function Test-Admin { + net session 2> $null | Out-Null; + return $?; +} + +<# + .SYNOPSIS + Removes desktop icons which apply to the specified pattern. + + .PARAMETER Pattern + The pattern to match the icons to delete. +#> +function Remove-DesktopIcon { + param( + [string] $Pattern + ) + + $path = "Desktop/$Pattern"; + + foreach ($userDir in @("~", $env:PUBLIC, "$env:SystemDrive/Users/Default")) { + $fullName = "$userDir/$path"; + + if (Test-Path -PathType Leaf $fullName) { + Remove-Item $fullName; + } + } +} + +<# + .SYNOPSIS + Adds a new shortcut to the start menu. + + .PARAMETER Name + The name of the icon to create. + + .PARAMETER Target + The file to link to. +#> +function Add-StartMenuIcon { + param( + [string] $Name, + [string] $Target + ) + + Import-Module KnownFolders; + Import-Module "$env:ChocolateyInstall/helpers/chocolateyInstaller.psm1"; + Install-ChocolateyShortcut -ShortcutFilePath "$((Get-KnownFolder "Common Programs").Path)/$Name.lnk" -TargetPath ((Get-Item $Target).FullName); +} + +<# + .SYNOPSIS + Removes icons from the task bar. + + .PARAMETER Pattern + The pattern of the icon names to remove. +#> +function Remove-TaskbarItem { + param( + [string] $Pattern + ) + + Import-Module -UseWindowsPowerShell PinnedItem; + Get-PinnedItem -Type TaskBar | Where-Object { $_.Name -like "$Pattern" } | ForEach-Object { Remove-PinnedItem $_ }; +} diff --git a/scripts/Windows/Scripts/Tasks.ps1 b/scripts/Windows/Scripts/Tasks.ps1 new file mode 100644 index 00000000..0c912ed6 --- /dev/null +++ b/scripts/Windows/Scripts/Tasks.ps1 @@ -0,0 +1,156 @@ +$null = New-Module { + . "$PSScriptRoot/Constants.ps1"; + . "$PSScriptRoot/Registry.ps1"; + . "$PSScriptRoot/../Types/OneShotTask.ps1"; + . "$PSScriptRoot/../../Common/Scripts/Config.ps1"; + $oneShotTaskName = "PortValhalla OneShot"; + $logName = "Application"; + $oneShotTrigger = 1337; + $taskOption = "OneShotTask"; + + $getErrorPath = { + Join-Path (Get-ArtifactRoot) "error.txt"; + }; + + $getUserName = { + "$(Get-SetupUser)OneShot"; + }; + + $taskSetter = { + param([Nullable[OneShotTask]] $Task) + Set-SetupOption $taskOption ([string]$Task); + }; + + <# + .SYNOPSIS + Gets the current OneShot task. + #> + function Get-OneShotTask { + $task = Get-SetupOption $taskOption; + + if ($task) { + return [OneShotTask]$task; + } else { + return $null; + } + } + + <# + .SYNOPSIS + Registers a task for listening to OneShot invocations. + #> + function Enable-OneShotListener { + $tempTask = "PortValhalla Temp"; + $user = & $getUserName; + $password = [string]([guid]::NewGuid()); + + $adminGroup = @{ + SID = [SecurityIdentifier]::new([WellKnownSidType]::BuiltinAdministratorsSid, $null); + }; + + $null = New-LocalUser -Name $user -Password (ConvertTo-SecureString -AsPlainText $password); + Add-LocalGroupMember -Member $user @adminGroup; + $logPath = Join-Path (Get-ArtifactRoot) "OneShotTask.log"; + $path = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"; + $null = New-Item -Force -ErrorAction SilentlyContinue $path; + Set-ItemProperty $path -Name $user -Value 0; + + $action = New-ScheduledTaskAction -Execute "pwsh" -Argument "-Command & { $([string](Get-StartupCommand)) } 2>&1 | Tee-Object -FilePath $(ConvertTo-Injection $logPath)"; + schtasks /Create /SC ONEVENT /EC $logName /MO "*[System[Provider[@Name='$logName'] and EventID=$($oneShotTrigger)]]" /TR cmd.exe /TN $tempTask; + $trigger = (Get-ScheduledTask $tempTask).Triggers; + $null = Register-ScheduledTask -Force $oneShotTaskName -Action $action -Trigger $trigger -RunLevel Highest -User $user -Password $password; + $null = Unregister-ScheduledTask -Confirm:$false $tempTask; + } + + <# + .SYNOPSIS + Removes the OneShot task. + #> + function Disable-OneShotListener { + Unregister-ScheduledTask -Confirm:$false $oneShotTaskName; + $user = Get-LocalUser (& $getUserName); + [string] $sid = $user.SID; + Remove-LocalUser $user; + Get-CimInstance Win32_UserProfile | Where-Object { $_.SID -eq $sid } | Remove-CimInstance; + } + + <# + .SYNOPSIS + Invokes a one-shot task. + + .PARAMETER Task + The task to run. + #> + function Invoke-OneShot { + param( + [OneShotTask] $Task + ) + + $errorPath = & $getErrorPath; + & $taskSetter $Task; + + & { + $identifier = "EventLog$oneShotTrigger"; + $log = [System.Diagnostics.EventLog]::new($logName); + $log.EnableRaisingEvents = $true; + + $null = Register-ObjectEvent -InputObject $log -EventName EntryWritten -Action { + $entry = $Event.SourceEventArgs.Entry; + $trigger = $Event.MessageData.Trigger; + $identifier = $Event.MessageData.Identifier; + + if ($entry.EventID -eq $trigger) { + $null = New-Event -SourceIdentifier $identifier; + } + } ` + -MessageData @{ + Trigger = $oneShotTrigger; + Identifier = $identifier; + }; + + Write-EventLog -LogName $logName -Source $logName -EventId $oneShotTrigger -Message "Starting OneShot task ``$(Get-OneShotTask)``…"; + + for ($i = 0; $i -lt 2; $i++) { + Remove-Event -EventIdentifier (Wait-Event -SourceIdentifier $identifier).EventIdentifier; + } + }; + + if (Test-Path $errorPath) { + $errorMessage = Get-Content $errorPath; + Remove-Item $errorPath; + + if ($errorMessage) { + Write-Error $errorMessage; + } + } + } + + <# + .SYNOPSIS + Executes the specified action and notifies the OneShot task executor. + #> + function Start-OneShot { + try { + Write-Host "Running OneShot task ``$(Get-OneShotTask)``"; + + switch (Get-OneShotTask) { + ([OneShotTask]::InitializeMSAccount) { + Initialize-UserCreation; + } + ([OneShotTask]::DisableUAC) { + Disable-UAC; + Register-Setup; + } + } + } + catch { + $errorPath = & $getErrorPath; + Set-Content -Path $errorPath -Value $Error; + Set-UserPermissions $errorPath; + } + finally { + & $taskSetter $null; + Write-EventLog -LogName $logName -Source $logName -EventId $oneShotTrigger -Message "The OneShot task ``$(Get-OneShotTask)`` finished."; + } + } +} diff --git a/scripts/Windows/Scripts/Users.ps1 b/scripts/Windows/Scripts/Users.ps1 index ba6bf6f4..0334e171 100644 --- a/scripts/Windows/Scripts/Users.ps1 +++ b/scripts/Windows/Scripts/Users.ps1 @@ -3,9 +3,11 @@ using namespace System.Security.Principal; $null = New-Module { . "$PSScriptRoot/Deployment.ps1"; + . "$PSScriptRoot/Registry.ps1"; + . "$PSScriptRoot/System.ps1"; + . "$PSScriptRoot/../Types/OneShotTask.ps1"; . "$PSScriptRoot/../../Common/Scripts/Config.ps1"; . "$PSScriptRoot/../../Common/Scripts/Operations.ps1"; - . "$PSScriptRoot/../../Common/Types/OneShotTask.ps1"; $loggedInUserOption = "LoggedInUser"; <# diff --git a/scripts/Windows/Scripts/WSL.ps1 b/scripts/Windows/Scripts/WSL.ps1 index eb84e838..bbce2654 100644 --- a/scripts/Windows/Scripts/WSL.ps1 +++ b/scripts/Windows/Scripts/WSL.ps1 @@ -1,5 +1,6 @@ . "$PSScriptRoot/Constants.ps1"; . "$PSScriptRoot/../Scripts/Security.ps1"; +. "$PSScriptRoot/../../Common/Scripts/SoftwareManagement.ps1"; <# .SYNOPSIS @@ -57,7 +58,6 @@ function Install-Wsl { wsl --install --no-launch; # Microsoft broke WSL - Quelle surprise! # ToDo: Remove this workaround once it's unbroken - . "$PSScriptRoot/../../Common/Scripts/Software.ps1"; Install-SetupPackage "https://github.com/microsoft/WSL/releases/download/2.3.17/wsl.2.3.17.0.x64.msi" -ArgumentList "/Quiet"; } @@ -125,3 +125,47 @@ function Unregister-WslDistribution { Uninstall-WslDistribution; Move-Item $tempDisk $wslDisk; } + +<# + .SYNOPSIS + Converts the specified path to linux and escapes it for the use in a script. + + .PARAMETER Path + The path to convert. +#> +function ConvertTo-LinuxPath { + param( + [string] $Path + ) + + & { + $ErrorActionPreference = 'Continue'; + $completed = $false; + + while (-not $completed) { + $job = Start-Job { + $env:Value = Resolve-Path $Using:Path; + $env:WSLENV = "Value/p"; + $result = wsl -- bash -c 'echo "$Value"'; + wsl -e printf "%q" "$result"; + }; + + $result = Receive-Job -Wait $job; + + + if ((Split-Path -Leaf $Path) -ne (Split-Path -Leaf $result)) { + Write-Error "The result of the path conversion of ``$Path`` was unexpected: ``$result``"; + continue; + } + + if ($job.State -ne ([System.Management.Automation.JobState]::Completed)) { + Write-Error "An error occurred while converting ``$Path`` to a Linux path.`nOutput: ``$result``"; + continue; + } + + $completed = $true; + } + + $result; + }; +} diff --git a/scripts/Windows/Software/Ext4Fsd/Main.ps1 b/scripts/Windows/Software/Ext4Fsd/Main.ps1 index 8cba965a..46e2f4bc 100644 --- a/scripts/Windows/Software/Ext4Fsd/Main.ps1 +++ b/scripts/Windows/Software/Ext4Fsd/Main.ps1 @@ -3,6 +3,7 @@ param ( [hashtable] $Arguments ) +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Types/InstallerAction.ps1"; diff --git a/scripts/Windows/Software/Firefox/Manage.ps1 b/scripts/Windows/Software/Firefox/Manage.ps1 index beb6105b..b9334605 100644 --- a/scripts/Windows/Software/Firefox/Manage.ps1 +++ b/scripts/Windows/Software/Firefox/Manage.ps1 @@ -4,6 +4,7 @@ param( ) . "$PSScriptRoot/../../Scripts/AppAssociations.ps1"; +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; Start-SoftwareInstaller @PSBoundParameters ` diff --git a/scripts/Windows/Software/LGHub/Manage.ps1 b/scripts/Windows/Software/LGHub/Manage.ps1 index 07403926..b0358d15 100644 --- a/scripts/Windows/Software/LGHub/Manage.ps1 +++ b/scripts/Windows/Software/LGHub/Manage.ps1 @@ -6,6 +6,7 @@ param( & { param ($Parameters) . "$PSScriptRoot/../../Scripts/Restoration.ps1"; + . "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; $configPath = "$env:LocalAppData/LGHUB"; diff --git a/scripts/Windows/Software/MSEdgeRedirect/Manage.ps1 b/scripts/Windows/Software/MSEdgeRedirect/Manage.ps1 index e87fa1df..55a7a32e 100644 --- a/scripts/Windows/Software/MSEdgeRedirect/Manage.ps1 +++ b/scripts/Windows/Software/MSEdgeRedirect/Manage.ps1 @@ -3,6 +3,7 @@ param( [hashtable] $Arguments ) +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; Start-SoftwareInstaller @PSBoundParameters ` diff --git a/scripts/Windows/Software/ManiaPlanet/Manage.ps1 b/scripts/Windows/Software/ManiaPlanet/Manage.ps1 index ccb93b76..d55e10df 100644 --- a/scripts/Windows/Software/ManiaPlanet/Manage.ps1 +++ b/scripts/Windows/Software/ManiaPlanet/Manage.ps1 @@ -6,6 +6,7 @@ param( & { param($Parameters) . "$PSScriptRoot/../../Scripts/Restoration.ps1"; + . "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Scripts/System.ps1"; $path = "$HOME/Documents/ManiaPlanet"; diff --git a/scripts/Windows/Software/Oh My Posh/Manage.ps1 b/scripts/Windows/Software/Oh My Posh/Manage.ps1 index 47784d60..84d5ce3a 100644 --- a/scripts/Windows/Software/Oh My Posh/Manage.ps1 +++ b/scripts/Windows/Software/Oh My Posh/Manage.ps1 @@ -3,6 +3,7 @@ param ( [hashtable] $Arguments ) +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Types/InstallerAction.ps1"; diff --git a/scripts/Windows/Software/PuTTY/Manage.ps1 b/scripts/Windows/Software/PuTTY/Manage.ps1 index a957cedc..41718da3 100644 --- a/scripts/Windows/Software/PuTTY/Manage.ps1 +++ b/scripts/Windows/Software/PuTTY/Manage.ps1 @@ -6,6 +6,7 @@ param( ) . "$PSScriptRoot/../../Scripts/Restoration.ps1"; +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Scripts/System.ps1"; diff --git a/scripts/Windows/Software/RetroArch/Manage.ps1 b/scripts/Windows/Software/RetroArch/Manage.ps1 index 6a94e002..c69e0870 100644 --- a/scripts/Windows/Software/RetroArch/Manage.ps1 +++ b/scripts/Windows/Software/RetroArch/Manage.ps1 @@ -6,6 +6,7 @@ param( & { param($Parameters) . "$PSScriptRoot/../../Scripts/Restoration.ps1"; + . "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Scripts/System.ps1"; $path = "C:/tools/RetroArch-Win64"; diff --git a/scripts/Windows/Software/Thunderbird/Manage.ps1 b/scripts/Windows/Software/Thunderbird/Manage.ps1 index dc11372a..37549b79 100644 --- a/scripts/Windows/Software/Thunderbird/Manage.ps1 +++ b/scripts/Windows/Software/Thunderbird/Manage.ps1 @@ -6,6 +6,7 @@ param( ) . "$PSScriptRoot/../../Scripts/AppAssociations.ps1"; +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; Start-SoftwareInstaller @PSBoundParameters ` diff --git a/scripts/Windows/Software/TobiiGameHub/Manage.ps1 b/scripts/Windows/Software/TobiiGameHub/Manage.ps1 index 1be0388e..e572c179 100644 --- a/scripts/Windows/Software/TobiiGameHub/Manage.ps1 +++ b/scripts/Windows/Software/TobiiGameHub/Manage.ps1 @@ -5,6 +5,7 @@ param( [hashtable] $Arguments ) +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Scripts/System.ps1"; diff --git a/scripts/Windows/Software/TobiiGhost/Manage.ps1 b/scripts/Windows/Software/TobiiGhost/Manage.ps1 index 9d0960a7..02f0255b 100644 --- a/scripts/Windows/Software/TobiiGhost/Manage.ps1 +++ b/scripts/Windows/Software/TobiiGhost/Manage.ps1 @@ -3,6 +3,7 @@ param( [hashtable] $Arguments ) +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Scripts/System.ps1"; diff --git a/scripts/Windows/Software/TrackMania Nations Forever/Manage.ps1 b/scripts/Windows/Software/TrackMania Nations Forever/Manage.ps1 index df75d5cc..fbbb4e51 100644 --- a/scripts/Windows/Software/TrackMania Nations Forever/Manage.ps1 +++ b/scripts/Windows/Software/TrackMania Nations Forever/Manage.ps1 @@ -7,6 +7,7 @@ param( & { param($Parameters) . "$PSScriptRoot/../../Scripts/Restoration.ps1"; + . "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; $path = "$HOME/Documents/TmForever"; diff --git a/scripts/Windows/Software/Ubiquiti UniFi Controller/Manage.ps1 b/scripts/Windows/Software/Ubiquiti UniFi Controller/Manage.ps1 index eb047168..3b64f46a 100644 --- a/scripts/Windows/Software/Ubiquiti UniFi Controller/Manage.ps1 +++ b/scripts/Windows/Software/Ubiquiti UniFi Controller/Manage.ps1 @@ -3,6 +3,7 @@ param( [hashtable] $Arguments ) +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Scripts/System.ps1"; diff --git a/scripts/Windows/Software/VisualStudio/Manage.ps1 b/scripts/Windows/Software/VisualStudio/Manage.ps1 index d430c02f..4afad841 100644 --- a/scripts/Windows/Software/VisualStudio/Manage.ps1 +++ b/scripts/Windows/Software/VisualStudio/Manage.ps1 @@ -7,6 +7,7 @@ param( param($parameters) . "$PSScriptRoot/../../Scripts/Restoration.ps1"; + . "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/BrowserAutomation.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Scripts/SoftwareManagement.ps1"; diff --git a/scripts/Windows/Software/WinSCP/Manage.ps1 b/scripts/Windows/Software/WinSCP/Manage.ps1 index 2d977288..dabe433a 100644 --- a/scripts/Windows/Software/WinSCP/Manage.ps1 +++ b/scripts/Windows/Software/WinSCP/Manage.ps1 @@ -6,6 +6,7 @@ param( ) . "$PSScriptRoot/../../Scripts/AppAssociations.ps1"; +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Scripts/System.ps1"; diff --git a/scripts/Windows/Software/aliae/Main.ps1 b/scripts/Windows/Software/aliae/Main.ps1 index 2143ea1c..341a329a 100644 --- a/scripts/Windows/Software/aliae/Main.ps1 +++ b/scripts/Windows/Software/aliae/Main.ps1 @@ -3,6 +3,7 @@ param ( [hashtable] $Arguments ) +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Types/InstallerAction.ps1"; diff --git a/scripts/Windows/Software/git/Manage.ps1 b/scripts/Windows/Software/git/Manage.ps1 index 320c0f93..1edc3858 100644 --- a/scripts/Windows/Software/git/Manage.ps1 +++ b/scripts/Windows/Software/git/Manage.ps1 @@ -3,6 +3,7 @@ param ( [hashtable] $Arguments ) +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Config.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Types/InstallerAction.ps1"; diff --git a/scripts/Windows/Software/osu!/Manage.ps1 b/scripts/Windows/Software/osu!/Manage.ps1 index f5db941b..6be11cef 100644 --- a/scripts/Windows/Software/osu!/Manage.ps1 +++ b/scripts/Windows/Software/osu!/Manage.ps1 @@ -3,6 +3,7 @@ param( [hashtable] $Arguments ) +. "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; & { diff --git a/scripts/Windows/Software/osu!lazer/Manage.ps1 b/scripts/Windows/Software/osu!lazer/Manage.ps1 index b6f8556d..726531ad 100644 --- a/scripts/Windows/Software/osu!lazer/Manage.ps1 +++ b/scripts/Windows/Software/osu!lazer/Manage.ps1 @@ -6,6 +6,7 @@ param( & { param($Parameters) . "$PSScriptRoot/../../Scripts/Restoration.ps1"; + . "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Scripts/System.ps1"; $path = "$env:AppData/osu"; diff --git a/scripts/Windows/Software/vscode/Main.ps1 b/scripts/Windows/Software/vscode/Main.ps1 index 96141b58..d091d5de 100644 --- a/scripts/Windows/Software/vscode/Main.ps1 +++ b/scripts/Windows/Software/vscode/Main.ps1 @@ -8,6 +8,7 @@ param( [hashtable] $Parameters ) + . "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Types/InstallerAction.ps1"; $base = "$PSScriptRoot/../../../Common/Software/vscode/Main.ps1"; diff --git a/scripts/Windows/Software/zoxide/Manage.ps1 b/scripts/Windows/Software/zoxide/Manage.ps1 index 07d5eb99..6a0cce02 100644 --- a/scripts/Windows/Software/zoxide/Manage.ps1 +++ b/scripts/Windows/Software/zoxide/Manage.ps1 @@ -8,6 +8,7 @@ param( [hashtable] $Parameters ) + . "$PSScriptRoot/../../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../../../Common/Scripts/Software.ps1"; . "$PSScriptRoot/../../../Common/Software/PowerShell/Profile.ps1"; . "$PSScriptRoot/../../../Common/Types/InstallerAction.ps1"; diff --git a/scripts/Common/Types/OneShotTask.ps1 b/scripts/Windows/Types/OneShotTask.ps1 similarity index 100% rename from scripts/Common/Types/OneShotTask.ps1 rename to scripts/Windows/Types/OneShotTask.ps1