├── requirements.txt ├── obs-studio ├── basic │ ├── profiles │ │ ├── Recording_AV1 │ │ │ ├── recordEncoder.json │ │ │ └── basic.ini │ │ └── Recording_NVENC │ │ │ ├── recordEncoder.json │ │ │ └── basic.ini │ └── scenes │ │ └── Laptop_dock.json └── Setup-OBS.ps1 ├── Hyper-V ├── Export-CurrentGuardian.ps1 ├── Optimize-OfflineVHDs.ps1 ├── Export-VMs.ps1 ├── Import-DestinationGuardian.ps1 └── Update-KeyProtectorForAllVMs.ps1 ├── Report-Readme-Template.txt ├── Test-GPUpdate.ps1 ├── configure-executionpolicy.bat ├── Update-Repos.ps1 ├── .gitignore ├── windows-xp └── README.md ├── windows-98 └── README.md ├── Profile ├── Microsoft.PowerShellISE_profile.ps1 ├── Setup-Profile.ps1 └── Profile.ps1 ├── Setup-FIDO2-SSH.ps1 ├── fix-fastboot.bat ├── Reset-YubiKey.ps1 ├── LICENSE ├── Copy-Floppy.ps1 ├── Eject-Drive.ps1 ├── Zero-Free-Space.ps1 ├── .github └── workflows │ └── main.yml ├── Sign-Script.ps1 ├── SSH └── Setup-SSH.ps1 ├── VPN ├── SecuritySettings.ps1 ├── Harden-VPN.ps1 └── Create-VPNProfile.ps1 ├── Repair-Computer.ps1 ├── Install-Repo.bat ├── Setup-Syncthing.ps1 ├── Run-PerfTest.ps1 ├── README.md ├── Report.ps1 ├── Maintenance.ps1 ├── Utils.ps1 └── Install-Software.ps1 /requirements.txt: -------------------------------------------------------------------------------- 1 | yubikey-manager 2 | -------------------------------------------------------------------------------- /obs-studio/basic/profiles/Recording_AV1/recordEncoder.json: -------------------------------------------------------------------------------- 1 | {"rate_control":"CQP"} -------------------------------------------------------------------------------- /obs-studio/basic/profiles/Recording_NVENC/recordEncoder.json: -------------------------------------------------------------------------------- 1 | {"rate_control":"CQP"} -------------------------------------------------------------------------------- /Hyper-V/Export-CurrentGuardian.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgenttiX/windows-scripts/HEAD/Hyper-V/Export-CurrentGuardian.ps1 -------------------------------------------------------------------------------- /Report-Readme-Template.txt: -------------------------------------------------------------------------------- 1 | This report was created on HOST at TIMESTAMP using Mika's reporting script: 2 | https://github.com/AgenttiX/windows-scripts/blob/master/Report.ps1 3 | -------------------------------------------------------------------------------- /Test-GPUpdate.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Update group policies and see that they are applied properly. 4 | #> 5 | 6 | gpupdate /force 7 | gpresult /h "report.html" /f 8 | .\report.html 9 | -------------------------------------------------------------------------------- /configure-executionpolicy.bat: -------------------------------------------------------------------------------- 1 | rem This script configures PowerShell to allow the execution of local custom scripts 2 | powershell -command "& {Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force}" 3 | -------------------------------------------------------------------------------- /Update-Repos.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Refresh Git repositories 4 | #> 5 | 6 | $Repos = Get-ChildItem -Directory ((Get-Item "${PSScriptRoot}").Parent.FullName) 7 | 8 | foreach ($Repo in $Repos) { 9 | Set-Location $Repo.FullName 10 | git pull 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.exe 3 | *.zip 4 | .idea 5 | downloads 6 | errors.log 7 | lib 8 | logs 9 | obs-studio/crashes 10 | obs-studio/global.ini 11 | obs-studio/plugin_config 12 | obs-studio/profiler_data 13 | obs-studio/updates 14 | obs-studio/user.ini 15 | reports 16 | reports.zip 17 | venv 18 | -------------------------------------------------------------------------------- /Hyper-V/Optimize-OfflineVHDs.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Optimize the VHDs of all VMs that are not currently running. 4 | .NOTES 5 | Based on 6 | https://dscottraynsford.wordpress.com/2015/04/07/multiple-vhdvhdx-optimization-using-powershell-workflows/ 7 | #> 8 | 9 | Get-VM | Where { $_.State -eq 'Off' } | Get-VMHardDiskDrive | Optimize-VHD -Mode Full 10 | -------------------------------------------------------------------------------- /obs-studio/Setup-OBS.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Configure Open Broadcaster Software (OBS) 4 | .LINK 5 | https://obsproject.com/ 6 | #> 7 | 8 | . "$(Join-Path $((Get-Item "${PSScriptRoot}").Parent.FullName) "Utils.ps1")" 9 | 10 | $OBSDirectory = "${env:AppData}\obs-studio" 11 | 12 | Show-Output "Creating junction to OBS config directory." 13 | New-Junction -Path "${OBSDirectory}" -Target "${PSScriptRoot}" 14 | -------------------------------------------------------------------------------- /Hyper-V/Export-VMs.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter(Mandatory=$true)] [string] $ExportPath, 3 | [string] $Server = "localhost" 4 | ) 5 | 6 | Write-Host "Virtual machines on $Server" 7 | Get-VM -ComputerName $Server 8 | 9 | Write-Host "Replication status" 10 | Get-VMReplication -ComputerName $Server 11 | 12 | $vms = Get-VMReplication -ComputerName $Server -ReplicationMode Primary | Get-VM -Name {$_.VMName} | Where-Object {$_.State -eq "off" } 13 | echo "Virtual machines to be exported" 14 | $vms 15 | 16 | $vms | Export-VM -Path $ExportPath 17 | -------------------------------------------------------------------------------- /windows-xp/README.md: -------------------------------------------------------------------------------- 1 | # Windows XP scripts 2 | These scripts and instructions are for Windows XP -based systems. 3 | 4 | ## Latest compatible software versions 5 | - FileZilla: [3.9.0.1](https://download.filezilla-project.org/client/) 6 | - Firefox: [52.9.0esr](https://www.mozilla.org/en-US/firefox/) 7 | - Geekbench: [2](https://www.geekbench.com/geekbench2/) 8 | - Git for Windows: [2.10.0](https://github.com/git-for-windows/git/releases/tag/v2.10.0.windows.1) 9 | - PowerShell: [2.0](https://www.microsoft.com/en-us/download/details.aspx?id=16818) 10 | -------------------------------------------------------------------------------- /windows-98/README.md: -------------------------------------------------------------------------------- 1 | # Windows 98 scripts 2 | These scripts and instructions are for Windows 98 -based systems. 3 | 4 | [KernelEX](http://kernelex.sourceforge.net/) 5 | is required for the installation of newer software. 6 | 7 | ## Latest compatible software versions 8 | - Firefox: 9.0.1 9 | - Futuremark: [legacy](https://benchmarks.ul.com/legacy-benchmarks) 10 | - Python: [2.5.4](https://www.python.org/downloads/release/python-254/) ([Stack Overflow](https://stackoverflow.com/a/3414288/10930376)) 11 | - [Service Pack 3](https://www.htasoft.com/u98sesp/) 12 | -------------------------------------------------------------------------------- /Profile/Microsoft.PowerShellISE_profile.ps1: -------------------------------------------------------------------------------- 1 | function Toggle-Comment { 2 | <# 3 | .LINK 4 | https://superuser.com/a/1730675 5 | #> 6 | $file = $psise.CurrentFile 7 | $text = $file.Editor.SelectedText 8 | if ($text.StartsWith("<#")) { 9 | $comment = $text.Substring(2).TrimEnd("#>") 10 | } 11 | else 12 | { 13 | $comment = "<#" + $text + "#>" 14 | } 15 | $file.Editor.InsertText($comment) 16 | } 17 | 18 | # https://superuser.com/a/1730675 19 | $psise.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('Toggle Comment', { Toggle-Comment }, 'CTRL+K') | Out-Null 20 | -------------------------------------------------------------------------------- /Setup-FIDO2-SSH.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Configure FIDO2 and SSH for a YubiKey 4 | .PARAMETER Elevated 5 | This parameter is for internal use to check whether an UAC prompt has already been attempted. 6 | #> 7 | 8 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Elevated", Justification="Used in utils")] 9 | param( 10 | [switch]$Elevated 11 | ) 12 | 13 | . "${PSScriptRoot}\Utils.ps1" 14 | Elevate($myinvocation.MyCommand.Definition) 15 | . ".\venv\Scripts\activate.ps1" 16 | 17 | Show-Output -ForegroundColor Cyan "Changing the FIDO2 PIN" 18 | ykman fido access change-pin 19 | Show-Output -ForegroundColor Cyan "Creating the SSH key" 20 | ssh-keygen -t ed25519-sk -O resident -O verify-required 21 | -------------------------------------------------------------------------------- /Profile/Setup-Profile.ps1: -------------------------------------------------------------------------------- 1 | Set-StrictMode -Latest 2 | $ErrorActionPreference = "Stop" 3 | 4 | . "$(Join-Path $((Get-Item "${PSScriptRoot}").Parent.FullName) "Utils.ps1")" 5 | 6 | Show-Output "Configuring PowerShell profile." 7 | 8 | $ProfileDir = Split-Path -Path "${profile}" -Parent 9 | 10 | $CreateNew = $true 11 | if (Test-Path -Path "${ProfileDir}") { 12 | if ((Get-Item -Path "${ProfileDir}" -Force).LinkType -eq "Junction") { 13 | $CreateNew = $false 14 | } else { 15 | Move-Item -Path "${ProfileDir}" "${ProfileDir}-old" 16 | } 17 | } 18 | 19 | if ($CreateNew) { 20 | New-item -ItemType "Junction" -Path "${ProfileDir}" -Target "${PSScriptRoot}" 21 | } 22 | 23 | Show-Output "PowerShell profile configuration is ready." 24 | -------------------------------------------------------------------------------- /fix-fastboot.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This is a script for fixing Android fastboot on Ryzen-based systems 3 | REM to get rid of the "Press any key to shutdown" error. 4 | REM Found from: 5 | REM https://forum.xda-developers.com/t/help-press-any-key-to-shutdown-in-fastboot.3816021/ 6 | REM Original file: 7 | REM https://drive.google.com/file/d/1NqPotx06yRuhPsOdEAdS4JNsk7qWWchF/view 8 | 9 | reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\usbflags\18D1D00D0100" /v "osvc" /t REG_BINARY /d "0000" /f 10 | reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\usbflags\18D1D00D0100" /v "SkipContainerIdQuery" /t REG_BINARY /d "01000000" /f 11 | reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\usbflags\18D1D00D0100" /v "SkipBOSDescriptorQuery" /t REG_BINARY /d "01000000" /f 12 | 13 | pause 14 | -------------------------------------------------------------------------------- /Hyper-V/Import-DestinationGuardian.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Imports the Hyper-V host guardian configuration of another host. 4 | Run this on the source host for migrating virtual machines. 5 | .PARAMETER Name 6 | The name of the destination guardian, e.g. DestinationGuardian 7 | .PARAMETER Path 8 | The path to the .xml file of the destination guardian 9 | .NOTES 10 | Based on 11 | https://threadfin.com/allowing-an-additional-host-to-run-a-vm-with-virtual-tpm/ 12 | https://gist.github.com/larsiwer/0eef34728d6697f8d5b899b1d866e573#file-update-keyprotectorforallvms-ps1 13 | #> 14 | 15 | param ( 16 | [Parameter(Mandatory=$true)][string]$Name, 17 | [Parameter(Mandatory=$true)][string]$Path 18 | ) 19 | 20 | # Import the destination guardian on the source machine 21 | Import-HgsGuardian -Path $Path -Name $Name -AllowExpired -AllowUntrustedRoot 22 | -------------------------------------------------------------------------------- /Reset-YubiKey.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Fully reset a YubiKey 4 | .PARAMETER Elevated 5 | This parameter is for internal use to check whether an UAC prompt has already been attempted. 6 | #> 7 | 8 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Elevated", Justification="Used in utils")] 9 | param( 10 | [switch]$Elevated 11 | ) 12 | 13 | . "${PSScriptRoot}\Utils.ps1" 14 | Elevate($myinvocation.MyCommand.Definition) 15 | . "${PSScriptRoot}\venv\Scripts\activate.ps1" 16 | 17 | Show-Output -ForegroundColor Cyan "Resetting FIDO" 18 | ykman fido reset 19 | Show-Output -ForegroundColor Cyan "Resetting OATH" 20 | ykman oath reset 21 | Show-Output -ForegroundColor Cyan "Resetting OpenPGP" 22 | ykman openpgp reset 23 | Show-Output -ForegroundColor Cyan "Resetting OTP slot 1" 24 | ykman otp delete 1 25 | Show-Output -ForegroundColor Cyan "Resetting OTP slot 2" 26 | ykman otp delete 2 27 | Show-Output -ForegroundColor Cyan "Resetting PIV" 28 | ykman piv reset 29 | Show-Output -ForegroundColor Green "Reset ready" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2023 Mika Mäki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Copy-Floppy.ps1: -------------------------------------------------------------------------------- 1 | . ".\utils.ps1" 2 | 3 | $FloppyFolder = "./floppy" 4 | if (-not (Test-Path "${FloppyFolder}")) { 5 | New-Item -Path "${FloppyFolder}" -ItemType "directory" 6 | } 7 | 8 | while ($true) { 9 | while ($true) { 10 | $Name = Read-Host "Please insert a new floppy and give a name for it" 11 | $FloppyPath = "${FloppyFolder}\${Name}" 12 | if (Test-Path "${FloppyPath}") { 13 | Show-Output "This folder already exists. Please use another name." 14 | } elseif (Test-Path "${Name}" -IsValid) { 15 | break 16 | } else { 17 | Show-Output "Invalid name. Please don't use special characters etc." 18 | } 19 | } 20 | 21 | Show-Output "Copying the floppy." 22 | Copy-Item -Path "A:\" -Destination "${FloppyPath}" -Recurse 23 | if (-not $?) { 24 | Show-Output "Copying the floppy failed. Please check the output folder to see which files were copied." 25 | continue 26 | } 27 | 28 | # $Format = Get-YesNo -Question "Floppy copied. Do you want to format the floppy?" 29 | # if ($Format) { 30 | # Format-Volume -DriveLetter "A" -FileSystem "FAT" -Full -Force 31 | # } 32 | # Show-Output "Floppy formatted. Please remove the floppy." 33 | Show-Output "Floppy copied. Please remove the floppy." 34 | } 35 | -------------------------------------------------------------------------------- /Eject-Drive.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Eject a disk such as a USB drive safely. 4 | .LINK 5 | https://superuser.com/questions/1750399/eject-dismount-a-drive-using-powershell 6 | https://serverfault.com/a/580298 7 | https://community.spiceworks.com/topic/2120667-how-to-safely-remove-usb-key-in-windows-server-core?page=1#entry-7617376 8 | .PARAMETER DriveLetter 9 | Drive letter, e.g. C 10 | #> 11 | 12 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWMICmdlet", "", Justification="Used on purpose since the WMI object does not have the proper methods")] 13 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "DriveLetter", Justification="Set to null on purpose")] 14 | param( 15 | [Parameter(Mandatory=$true)][char]$DriveLetter 16 | ) 17 | 18 | # This works on Windows 10 but seems to have no effect on Hyper-V Server 2019 19 | # $driveEject = New-Object -comObject Shell.Application 20 | # $driveEject.Namespace(17).ParseName("${DriveLetter}:").InvokeVerb("Eject") 21 | 22 | # WMI should be replaced with CIM 23 | # when a solution is found for calling the proper ejection methdods on the CIM object. 24 | # $vol = Get-CimInstance Win32_Volume | Where-Object {$_.Name -eq "${DriveLetter}:\"} 25 | $vol = Get-WmiObject -Class Win32_Volume | Where-Object {$_.Name -eq "${DriveLetter}:\"} 26 | $vol.DriveLetter = $null 27 | $vol.Put() 28 | $vol.Dismount($false, $false) 29 | -------------------------------------------------------------------------------- /Zero-Free-Space.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Zero free space on the disk. 4 | .DESCRIPTION 5 | Indended for VirtualBox guests as a preparation for compacting the disk image. 6 | .LINK 7 | https://superuser.com/questions/529149/how-to-compact-virtualboxs-vdi-file-size 8 | .PARAMETER DriveLetter 9 | Name of the drive to be cleaned 10 | #> 11 | 12 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Elevated", Justification="Used in utils")] 13 | param( 14 | [Parameter(Mandatory=$true)] [string]$DriveLetter, 15 | [switch]$Elevated 16 | ) 17 | 18 | . "${PSScriptRoot}\Utils.ps1" 19 | Elevate($myinvocation.MyCommand.Definition) 20 | 21 | if (! (Test-Path "./lib/SDelete")) { 22 | New-Item -Path "." -Name "lib" -ItemType "directory" -Force 23 | 24 | $SDeleteUri = "https://download.sysinternals.com/files/SDelete.zip" 25 | $SDeleteZipPath = "./lib/SDelete.zip" 26 | 27 | # https://docs.microsoft.com/en-us/sysinternals/downloads/sdelete 28 | Invoke-WebRequest -Uri $SDeleteUri -OutFile $SDeleteZipPath 29 | Expand-Archive -Path $SDeleteZipPath -DestinationPath "./lib/SDelete" 30 | } 31 | 32 | if ([Environment]::Is64BitOperatingSystem) { 33 | Show-Output "64-bit operating system detected. Using 64-bit SDelete." 34 | ./lib/SDelete/sdelete64.exe "${DriveLetter}:\" -z 35 | } else { 36 | Show-Output "32-bit operating system detected. Using 32-bit SDelete." 37 | ./lib/SDelete/sdelete.exe "${DriveLetter}:\" -z 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: push 3 | 4 | jobs: 5 | lint: 6 | name: Lint with PSScriptAnalyzer 7 | runs-on: ubuntu-latest 8 | timeout-minutes: 5 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | - name: Install PSScriptAnalyzer module 13 | shell: pwsh 14 | run: | 15 | Set-PSRepository PSGallery -InstallationPolicy Trusted 16 | Install-Module PSScriptAnalyzer -ErrorAction Stop 17 | - name: Lint with PSScriptAnalyzer 18 | shell: pwsh 19 | run: | 20 | Invoke-ScriptAnalyzer -Path *.ps1 -Recurse -Outvariable issues 21 | $errors = $issues.Where({$_.Severity -eq 'Error'}) 22 | $warnings = $issues.Where({$_.Severity -eq 'Warning'}) 23 | if ($errors) { 24 | Write-Error "There were $($errors.Count) errors and $($warnings.Count) warnings total." -ErrorAction Stop 25 | } else { 26 | Write-Output "There were $($errors.Count) errors and $($warnings.Count) warnings total." 27 | } 28 | report: 29 | name: Run reporting script 30 | runs-on: windows-latest 31 | timeout-minutes: 15 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v4 35 | - name: Run reporting script 36 | shell: powershell 37 | run: .\Report.ps1 38 | - name: Upload results 39 | uses: actions/upload-artifact@v4 40 | with: 41 | name: Report 42 | path: Reports 43 | if-no-files-found: error 44 | -------------------------------------------------------------------------------- /Sign-Script.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Adds an Authenticode signature to a PowerShell script or other file. 4 | .DESCRIPTION 5 | When the PowerShell execution policy is set to RemoteSigned or higher, 6 | executing remote scripts requires that they have a valid signature. 7 | To sign scripts you need to have a code signing certificate that is trusted by the client machines. 8 | This certificate can be generated with e.g. Active Directory Certificate Services. 9 | .LINK 10 | https://adamtheautomator.com/how-to-sign-powershell-script/ 11 | .LINK 12 | https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-authenticodesignature 13 | #> 14 | param( 15 | [Parameter(Position=0, Mandatory=$true)][string]$FilePath, 16 | [string]$CertPath, 17 | [string]$TimestampServer = "http://timestamp.digicert.com" 18 | ) 19 | 20 | # Get the code-signing certificate from the local computer's certificate store 21 | if ($PSBoundParameters.ContainsKey("CertPath")) { 22 | $Certificate = Get-item "${CertPath}" 23 | } else { 24 | $Certificate = Get-ChildItem "Cert:\CurrentUser\My" | Where-Object {$_.EnhancedKeyUsageList.FriendlyName -eq "Code Signing"} 25 | } 26 | if ($null -eq $Certificate) { 27 | Write-Output "Could not sign, as no signing certificate was found." 28 | return 29 | } 30 | 31 | # Adding a timestamp ensures that your code will not expire when the signing certificate expires. 32 | Set-AuthenticodeSignature -FilePath "${FilePath}" -Certificate $Certificate -HashAlgorithm "SHA256" -TimestampServer "${TimestampServer}" 33 | -------------------------------------------------------------------------------- /Profile/Profile.ps1: -------------------------------------------------------------------------------- 1 | # This file gets loaded by both PowerShell and PowerShell ISE 2 | 3 | # function Enable-SSHAgent { 4 | # . "${env:ProgramFiles}\Git\cmd\start-ssh-agent.cmd" 5 | # } 6 | 7 | # Import the Chocolatey Profile that contains the necessary code to enable 8 | # tab-completions to function for `choco`. 9 | # Be aware that if you are missing these lines from your profile, tab completion 10 | # for `choco` will not function. 11 | # See https://ch0.co/tab-completion for details. 12 | $ChocolateyProfile = "${env:ChocolateyInstall}\helpers\chocolateyProfile.psm1" 13 | if (Test-Path("${ChocolateyProfile}")) { 14 | Import-Module "${ChocolateyProfile}" 15 | } 16 | 17 | $SSHAgentStarted = $false 18 | if (Get-Process | Where {$_.Name -eq "ssh-agent"}) {} else { 19 | ssh-agent > "${Env:USERPROFILE}\.ssh-agent-info" | Out-Null 20 | $SSHAgentStarted = $true 21 | } 22 | if ($SSHAgentStarted -or (-not [Environment]::GetEnvironmentVariable("SSH_AUTH_SOCK", "User"))) { 23 | $SSHAgentInfo = Get-Content -Path "${Env:USERPROFILE}\.ssh-agent-info" 24 | $Env:SSH_AUTH_SOCK = $SSHAgentInfo.split("[`n=;]")[1] 25 | $Env:SSH_AGENT_PID = $SSHAgentInfo.split("[`n=;]")[5] 26 | } 27 | if ($SSHAgentStarted) { 28 | & "${Env:USERPROFILE}\Git\private-scripts\ssh\Setup-Agent.ps1" | Out-Null 29 | } 30 | 31 | if ($env:ComputerName -eq "agx-z2e-win") { 32 | function Set-Monitors($Name) { 33 | $FilePath = "${PSScriptRoot}\DisplayConfig\${Name}.xml" 34 | if (Test-Path $FilePath) { 35 | Import-Module DisplayConfig 36 | } else { 37 | Write-Information "The monitor configuration was not found at `"${FilePath}`"". 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SSH/Setup-SSH.ps1: -------------------------------------------------------------------------------- 1 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Elevated", Justification="Used in utils")] 2 | param( 3 | [Parameter(mandatory=$true)][string]$RepoName, 4 | [Parameter(mandatory=$false)][switch]$Elevated 5 | ) 6 | 7 | $RepoPath = (Get-Item "${PSScriptRoot}").Parent.FullName 8 | $GitPath = (Get-Item "${PSScriptRoot}").Parent.Parent.FullName 9 | $UtilsPath = "${RepoPath}\Utils.ps1" 10 | if (-not (Test-Path "${UtilsPath}")) { 11 | Write-Host "Utils.ps1 was not found at ${UtilsPath}" 12 | return 13 | } 14 | . "${UtilsPath}" 15 | 16 | $ConfigDir = "${GitPath}\${RepoName}\ssh" 17 | if (-not (Test-Path "${ConfigDir}")) { 18 | Write-Host "The SSH configuration was not found at ${ConfigDir}" 19 | return 20 | } 21 | 22 | $ControlMasterDir = "${ConfigDir}\controlmasters" 23 | if (-not (Test-Path "${ControlMasterDir}")) { 24 | Write-Host "The SSH ControlMasters directory was not found. Creating it at `"${ControlMasterDir}`"." 25 | New-Item -Path "${ControlMasterDir}" -ItemType "directory" 26 | } 27 | 28 | $SSHDir = "${HOME}\.ssh" 29 | 30 | # This should be as early as possible to avoid loading the function definitions etc. twice. 31 | # Elevate($myinvocation.MyCommand.Definition) 32 | 33 | # New-Item -ItemType "directory" -Path "${SSHDir}" 34 | # Write-Output "If you get permission errors, enable developer mode in Windows settings." 35 | # New-Item -ItemType "SymbolicLink" -Path "${SSHDir}\authorized_keys" -Target "${PSScriptRoot}\authorized_keys" 36 | # New-Item -ItemType "SymbolicLink" -Path "${SSHDir}\config" -Target "${PSScriptRoot}\config" 37 | 38 | Show-Output "Creating junction to SSH config directory." 39 | New-Junction -Path "${SSHDir}" -Target "${ConfigDir}" 40 | 41 | Show-Output "Creating junction to SSH config.d directory." 42 | New-Junction -Path "${ConfigDir}\config.d" -Target "${GitPath}\linux-scripts\ssh\config.d" 43 | 44 | # $Service = Get-Service -Name "ssh-agent" -ErrorAction SilentlyContinue 45 | # if ($Service.Length -gt 0) { 46 | # Remove-Service -Name "ssh-agent" 47 | # } 48 | # New-Service -Name "ssh-agent" -BinaryPathName "${env:ProgramFiles}\Git\cmd\start-ssh-agent.cmd" -StartupType "Automatic" 49 | # Start-Service -Name "ssh-agent" 50 | -------------------------------------------------------------------------------- /Hyper-V/Update-KeyProtectorForAllVMs.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Adds the key protector of the destination host guardian for migrating Hyper-V virtual machines. 4 | Run this on the source host. 5 | .PARAMETER Name 6 | The name of the destination guardian, e.g. DestinationGuardian 7 | .NOTES 8 | Based on 9 | https://threadfin.com/allowing-an-additional-host-to-run-a-vm-with-virtual-tpm/ 10 | https://gist.github.com/larsiwer/0eef34728d6697f8d5b899b1d866e573#file-update-keyprotectorforallvms-ps1 11 | #> 12 | 13 | param ( 14 | [Parameter(Mandatory=$true)][string]$Name 15 | ) 16 | 17 | $destinationguardianname = $Name 18 | 19 | # Get destination guardian 20 | $destinationguardian = Get-HgsGuardian -Name $destinationguardianname 21 | 22 | # Check if system is running in HGS local mode 23 | If ((Get-HgsClientConfiguration | select -ExpandProperty Mode) -ne "Local") 24 | { 25 | throw "HGS local mode required to update the key protector" 26 | } 27 | 28 | # Loop through all VMs existing on the local system 29 | foreach ($vm in (Get-VM)) 30 | { 31 | # If the VM has the vTPM enabled, update the key protector 32 | If ((Get-VMSecurity -VM $vm).TpmEnabled -eq $true) 33 | { 34 | # Retrieve the current key protector for the virtual machine 35 | $keyprotector = ConvertTo-HgsKeyProtector -Bytes (Get-VMKeyProtector -VM $vm) 36 | 37 | # Check if the current system has the right owner keys present 38 | If ($keyprotector.Owner.HasPrivateSigningKey) 39 | { 40 | # Add the destination UntrustedGuardian to the key protector 41 | $newkeyprotector = Grant-HgsKeyProtectorAccess -KeyProtector $keyprotector -Guardian $destinationguardian ` 42 | -AllowUntrustedRoot -AllowExpired 43 | 44 | Write-Output "Updating key protector for $($vm.Name)" 45 | # Apply the updated key protector to VM 46 | Set-VMKeyProtector -VM $vm -KeyProtector $newkeyprotector.RawData 47 | } 48 | else 49 | { 50 | # Owner key information is not present 51 | Write-Warning "Skipping $($vm.Name) - Owner key information is not present" 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /VPN/SecuritySettings.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Security settings for IKEv2 VPN 4 | .DESCRIPTION 5 | The VPN security settings are needed in several scripts, and therefore it's the easiest to store them in one place. 6 | .LINK 7 | https://directaccess.richardhicks.com/2018/12/10/always-on-vpn-ikev2-security-configuration/ 8 | .LINK 9 | https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-about-compliance-crypto 10 | .LINK 11 | https://learn.microsoft.com/en-us/powershell/module/vpnclient/set-vpnconnectionipsecconfiguration 12 | #> 13 | 14 | Write-Host "Loading VPN security settings from file." 15 | 16 | # "Specifies authentication header (AH) transform in the IPsec policy." 17 | $AuthenticationTransformConstants = "SHA256128" 18 | # $AuthenticationTransformConstants = "GCMAES256" 19 | # "Specifies Encapsulating Security Payload (ESP) cipher transform in the IPsec policy." 20 | $CipherTransformConstants = "AES256" 21 | # $CipherTransformConstants = "GCMAES256" 22 | # By default Windows uses Group2, which is 1024-bit and therefore not secure. 23 | # https://weakdh.org/ 24 | # As of 2023, Group14 (2048-bit MODP) is the bare minimum acceptable. 25 | # $DHGroup = "Group14" 26 | # The Group24 (2048-bit MODP with a 256-bit prime order subgroup) provides a security level of 112 bits 27 | # for symmetric encryption, and is therefore not sufficient for AES-256. 28 | # https://datatracker.ietf.org/doc/html/rfc5114#section-4 29 | # As of 2023, ECP384 is the strongest supported by Windows 11. 30 | $DHGroup = "ECP384" 31 | # Specifying GCMAES256 causes VPN_Profile.ps1 to fail with the error message 32 | # "A general error occurred that is not covered by a more specific error code." 33 | # This seems to be a bug in Windows 11. 34 | # GCMAES cannot be combined with AES, and therefore the other settings have to use AES-CCM as well. 35 | # $EncryptionMethod = "GCMAES256" 36 | $EncryptionMethod = "AES256" 37 | # $IntegrityCheckMethod = "SHA256" 38 | $IntegrityCheckMethod = "SHA384" 39 | # "DHGroup2048 & PFS2048 are the same as Diffie-Hellman Group 14 in IKE and IPsec PFS." 40 | # $PfsGroup = "PFS2048" 41 | $PfsGroup = "ECP384" 42 | 43 | # "IKEv2 Main Mode SA lifetime is fixed at 28,800 seconds on the Azure VPN gateways." 44 | $SALifeTimeSeconds = 28800 45 | $MMSALifeTimeSeconds = 86400 46 | $SADataSizeForRenegotiationKilobytes = 1024000 47 | -------------------------------------------------------------------------------- /Repair-Computer.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Fix various issues with Windows 4 | #> 5 | 6 | . "${PSScriptRoot}\Utils.ps1" 7 | Elevate($MyInvocation.MyCommand.Definition) 8 | 9 | Start-Transcript -Path "${LogPath}\Repair-Computer_$(Get-Date -Format "yyyy-MM-dd_HH-mm").txt" 10 | 11 | Show-Output -ForegroundColor Cyan "Running Mika's repair script." 12 | 13 | Show-Output -ForegroundColor Cyan "Ensuring that Windows is activated." 14 | slmgr /ato 15 | 16 | Show-Output -ForegroundColor Cyan "Running Windows System File Checker (SFC)." 17 | sfc /scannow 18 | 19 | Show-Output -ForegroundColor Cyan "Running DISM scan." 20 | Dism /Online /Cleanup-Image /ScanHealth 21 | 22 | Show-Output -ForegroundColor Cyan "Running DISM check." 23 | Dism /Online /Cleanup-Image /CheckHealth 24 | 25 | $Reply = Read-Host -Prompt "Do you want to run DISM repair? If no errors were found by the DISM scans above, this is not needed." 26 | if ( $Reply -match "[yY]" ) { 27 | Show-Output -ForegroundColor Cyan "Running DISM repair." 28 | Dism /Online /Cleanup-Image /RestoreHealth 29 | } else { 30 | Show-Output -ForegroundColor Cyan "Interpreting answer as a no. Skipping DISM repair." 31 | } 32 | 33 | Show-Output "Running DISM component store analysis." 34 | Dism /Online /Cleanup-Image /AnalyzeComponentStore 35 | 36 | $Reply = Read-Host -Prompt "Do you want to run DISM start component cleanup? If no errors were found by the DISM analysis above, this is not needed." 37 | if ( $Reply -match "[yY]" ) { 38 | Show-Output -ForegroundColor Cyan "Running DISM start component cleanup." 39 | DISM.exe /Online /Cleanup-Image /StartComponentCleanup 40 | } else { 41 | Show-Output -ForegroundColor Cyan "Interpreting answer as a no. Skipping DISM start component cleanup." 42 | } 43 | 44 | if (Test-CommandExists "CompatTelRunner.exe") { 45 | Show-Output "Running Windows telemetry to fix the availability of major Windows updates." 46 | CompatTelRunner.exe -m:appraiser.dll -f:DoScheduledTelemetryRun 47 | } else { 48 | Show-Output "The command `"CompatTelRunner.exe`" was not found." 49 | } 50 | 51 | Show-Output -ForegroundColor Cyan "Running CHKDSK." 52 | chkdsk /R 53 | 54 | Show-Output -ForegroundColor Cyan "Running Windows memory diagnostics." 55 | Start-Process -NoNewWindow MdSched 56 | 57 | Show-Output -ForegroundColor Green "The repair script is ready." 58 | Stop-Transcript 59 | -------------------------------------------------------------------------------- /obs-studio/basic/profiles/Recording_AV1/basic.ini: -------------------------------------------------------------------------------- 1 | [General] 2 | Name=Recording (AV1) 3 | 4 | [Output] 5 | Mode=Advanced 6 | FilenameFormatting=%CCYY-%MM-%DD %hh-%mm-%ss 7 | DelayEnable=false 8 | DelaySec=20 9 | DelayPreserve=true 10 | Reconnect=true 11 | RetryDelay=2 12 | MaxRetries=25 13 | BindIP=default 14 | NewSocketLoopEnable=false 15 | LowLatencyEnable=false 16 | 17 | [Stream1] 18 | IgnoreRecommended=false 19 | 20 | [SimpleOutput] 21 | FilePath=C:\\Users\\mika.maki\\Videos 22 | RecFormat2=mkv 23 | VBitrate=2500 24 | ABitrate=160 25 | UseAdvanced=false 26 | Preset=veryfast 27 | NVENCPreset2=p5 28 | RecQuality=Small 29 | RecRB=false 30 | RecRBTime=20 31 | RecRBSize=512 32 | RecRBPrefix=Replay 33 | StreamAudioEncoder=aac 34 | RecAudioEncoder=aac 35 | RecTracks=1 36 | StreamEncoder=nvenc 37 | RecEncoder=nvenc 38 | 39 | [AdvOut] 40 | ApplyServiceSettings=true 41 | UseRescale=false 42 | TrackIndex=1 43 | VodTrackIndex=2 44 | Encoder=obs_x264 45 | RecType=Standard 46 | RecFilePath=C:\\Users\\mika.maki\\Videos 47 | RecFormat2=mkv 48 | RecUseRescale=false 49 | RecTracks=1 50 | RecEncoder=ffmpeg_aom_av1 51 | FLVTrack=1 52 | FFOutputToFile=true 53 | FFFilePath=C:\\Users\\mika.maki\\Videos 54 | FFVBitrate=2500 55 | FFVGOPSize=250 56 | FFUseRescale=false 57 | FFIgnoreCompat=false 58 | FFABitrate=160 59 | FFAudioMixes=1 60 | Track1Bitrate=160 61 | Track2Bitrate=160 62 | Track3Bitrate=160 63 | Track4Bitrate=160 64 | Track5Bitrate=160 65 | Track6Bitrate=160 66 | RecSplitFileTime=15 67 | RecSplitFileSize=2048 68 | RecRB=false 69 | RecRBTime=20 70 | RecRBSize=512 71 | AudioEncoder=ffmpeg_aac 72 | RecAudioEncoder=ffmpeg_aac 73 | RecSplitFileType=Time 74 | FFFormat= 75 | FFFormatMimeType= 76 | FFVEncoderId=0 77 | FFVEncoder= 78 | FFAEncoderId=0 79 | FFAEncoder= 80 | 81 | [Video] 82 | BaseCX=1920 83 | BaseCY=1080 84 | OutputCX=1920 85 | OutputCY=1080 86 | FPSType=0 87 | FPSCommon=60 88 | FPSInt=30 89 | FPSNum=30 90 | FPSDen=1 91 | ScaleType=bicubic 92 | ColorFormat=NV12 93 | ColorSpace=709 94 | ColorRange=Full 95 | SdrWhiteLevel=300 96 | HdrNominalPeakLevel=1000 97 | 98 | [Audio] 99 | MonitoringDeviceId=default 100 | MonitoringDeviceName=Default 101 | SampleRate=48000 102 | ChannelSetup=Stereo 103 | MeterDecayRate=23.53 104 | PeakMeterType=0 105 | 106 | [Panels] 107 | CookieId=4235D4850E6FCD57 108 | -------------------------------------------------------------------------------- /obs-studio/basic/profiles/Recording_NVENC/basic.ini: -------------------------------------------------------------------------------- 1 | [General] 2 | Name=Recording (NVENC) 3 | 4 | [Output] 5 | Mode=Advanced 6 | FilenameFormatting=%CCYY-%MM-%DD %hh-%mm-%ss 7 | DelayEnable=false 8 | DelaySec=20 9 | DelayPreserve=true 10 | Reconnect=true 11 | RetryDelay=2 12 | MaxRetries=25 13 | BindIP=default 14 | NewSocketLoopEnable=false 15 | LowLatencyEnable=false 16 | 17 | [Stream1] 18 | IgnoreRecommended=false 19 | 20 | [SimpleOutput] 21 | FilePath=C:\\Users\\mika.maki\\Videos 22 | RecFormat2=mkv 23 | VBitrate=2500 24 | ABitrate=160 25 | UseAdvanced=false 26 | Preset=veryfast 27 | NVENCPreset2=p5 28 | RecQuality=Small 29 | RecRB=false 30 | RecRBTime=20 31 | RecRBSize=512 32 | RecRBPrefix=Replay 33 | StreamAudioEncoder=aac 34 | RecAudioEncoder=aac 35 | RecTracks=1 36 | StreamEncoder=nvenc 37 | RecEncoder=nvenc 38 | 39 | [AdvOut] 40 | ApplyServiceSettings=true 41 | UseRescale=false 42 | TrackIndex=1 43 | VodTrackIndex=2 44 | Encoder=obs_x264 45 | RecType=Standard 46 | RecFilePath=C:\\Users\\mika.maki\\Videos 47 | RecFormat2=mkv 48 | RecUseRescale=false 49 | RecTracks=1 50 | RecEncoder=jim_hevc_nvenc 51 | FLVTrack=1 52 | FFOutputToFile=true 53 | FFFilePath=C:\\Users\\mika.maki\\Videos 54 | FFVBitrate=2500 55 | FFVGOPSize=250 56 | FFUseRescale=false 57 | FFIgnoreCompat=false 58 | FFABitrate=160 59 | FFAudioMixes=1 60 | Track1Bitrate=160 61 | Track2Bitrate=160 62 | Track3Bitrate=160 63 | Track4Bitrate=160 64 | Track5Bitrate=160 65 | Track6Bitrate=160 66 | RecSplitFileTime=15 67 | RecSplitFileSize=2048 68 | RecRB=false 69 | RecRBTime=20 70 | RecRBSize=512 71 | AudioEncoder=ffmpeg_aac 72 | RecAudioEncoder=ffmpeg_aac 73 | RecFileNameWithoutSpace=true 74 | RecSplitFileType=Time 75 | FFFormat= 76 | FFFormatMimeType= 77 | FFVEncoderId=0 78 | FFVEncoder= 79 | FFAEncoderId=0 80 | FFAEncoder= 81 | 82 | [Video] 83 | BaseCX=1920 84 | BaseCY=1080 85 | OutputCX=1920 86 | OutputCY=1080 87 | FPSType=0 88 | FPSCommon=60 89 | FPSInt=30 90 | FPSNum=30 91 | FPSDen=1 92 | ScaleType=bicubic 93 | ColorFormat=NV12 94 | ColorSpace=709 95 | ColorRange=Full 96 | SdrWhiteLevel=300 97 | HdrNominalPeakLevel=1000 98 | 99 | [Audio] 100 | MonitoringDeviceId=default 101 | MonitoringDeviceName=Default 102 | SampleRate=48000 103 | ChannelSetup=Stereo 104 | MeterDecayRate=23.53 105 | PeakMeterType=0 106 | 107 | [Panels] 108 | CookieId=CDE61F285F49DEFA 109 | -------------------------------------------------------------------------------- /Install-Repo.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo This script is for setting up Mika's Windows scripts. 3 | 4 | NET SESSION >nul 2>&1 5 | IF %ERRORLEVEL% NEQ 0 ( 6 | echo This script should be run as administrator. Please right-click this script and select "Run as administrator". 7 | pause 8 | exit 1 9 | ) 10 | 11 | SET /P GLOBALYESNO=Do you want to install globally for all users? [y/n] (Write "y" and press enter, unless you are developing the scripts yourself.) 12 | IF /I "%GLOBALYESNO%" EQU "y" ( 13 | SET /A GLOBALINSTALL=1 14 | SET BASEPATH=%SYSTEMDRIVE% 15 | )ELSE ( 16 | IF /I "%GLOBALYESNO%" EQU "n" ( 17 | SET /A GLOBALINSTALL=0 18 | SET BASEPATH=%USERPROFILE% 19 | ) ELSE ( 20 | echo Invalid selection: %GLOBALYESNO% 21 | exit /b 1 22 | ) 23 | ) 24 | 25 | echo Allowing PowerShell scripts 26 | powershell -Command "& {Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force}" 27 | 28 | echo Current path of choco: 29 | WHERE choco 30 | IF %ERRORLEVEL% EQU 0 ( 31 | echo Chocolatey seems to be already installed. 32 | ) ELSE ( 33 | echo Installing Chocolatey 34 | powershell -command "Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))" 35 | REM Load Chocolatey command 36 | CALL %PROGRAMDATA%\chocolatey\bin\RefreshEnv.cmd 37 | ) 38 | echo Current path for git: 39 | WHERE git 40 | IF %ERRORLEVEL% EQU 0 ( 41 | echo Git seems to be already installed. 42 | ) ELSE ( 43 | echo Installing Git 44 | choco upgrade git -y 45 | REM Load Git command 46 | CALL %PROGRAMDATA%\chocolatey\bin\RefreshEnv.cmd 47 | ) 48 | 49 | IF exist %BASEPATH%\Git ( 50 | echo The Git folder seems to already exist. 51 | ) ELSE ( 52 | echo Creating a folder for Git repositories. 53 | mkdir %BASEPATH%\Git 54 | ) 55 | IF exist %BASEPATH%\Git\windows-scripts ( 56 | echo The repository seems to already exist. 57 | echo Performing "git pull" to update the scripts. 58 | cd %BASEPATH%\Git\windows-scripts 59 | git pull 60 | ) ELSE ( 61 | echo Cloning the repository 62 | cd %BASEPATH%\Git 63 | git clone "https://github.com/AgenttiX/windows-scripts" 64 | ) 65 | 66 | echo Marking the repository directory to be safe for Git. 67 | git config --global --add safe.directory %BASEPATH%\Git\windows-scripts 68 | 69 | echo Creating shortcuts and scheduled task 70 | powershell -File %BASEPATH%\Git\windows-scripts\Maintenance.ps1 -SetupOnly 71 | 72 | echo Opening the scripts folder in File Explorer. 73 | %SYSTEMROOT%\explorer.exe %BASEPATH%\Git\windows-scripts 74 | echo The setup is ready. You can close this window now. 75 | pause 76 | -------------------------------------------------------------------------------- /VPN/Harden-VPN.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Fix the security parameters of a Windows IKEv2 VPN connection. 4 | .DESCRIPTION 5 | Windows uses insecure Diffie-Hellmann parameters and other security settings by default. This script fixes it. 6 | .LINK 7 | https://docs.microsoft.com/fi-fi/windows/security/identity-protection/vpn/how-to-configure-diffie-hellman-protocol-over-ikev2-vpn-connections 8 | .LINK 9 | https://directaccess.richardhicks.com/2018/12/10/always-on-vpn-ikev2-security-configuration/ 10 | .LINK 11 | https://weakdh.org/ 12 | .LINK 13 | https://docs.strongswan.org/docs/5.9/interop/windowsClients.html#strong_ke 14 | #> 15 | param( 16 | [string]$ConnectionName = $null, 17 | [switch]$Reset = $false 18 | ) 19 | . "${PSScriptRoot}\SecuritySettings.ps1" 20 | 21 | $ProductType = (Get-CimInstance -ClassName Win32_OperatingSystem).ProductType 22 | 23 | if (($ProductType -eq 2) -or ($ProductType -eq 3)) { 24 | Write-Output "This seems to be a server. Configuring VPN server." 25 | if ($Reset) { 26 | Write-Output "Resetting to default settings." 27 | Set-VpnServerConfiguration -RevertToDefault -PassThru 28 | Restart-Service RemoteAccess -PassThru 29 | return 30 | } 31 | Set-VpnServerConfiguration ` 32 | -TunnelType IKEv2 ` 33 | -CustomPolicy ` 34 | -AuthenticationTransformConstants "${AuthenticationTransformConstants}" ` 35 | -CipherTransformConstants "${CipherTransformConstants}" ` 36 | -DHGroup "${DHGroup}" ` 37 | -EncryptionMethod "${EncryptionMethod}" ` 38 | -IntegrityCheckMethod "${IntegrityCheckMethod}" ` 39 | -PfsGroup "${PfsGroup}" ` 40 | -SALifeTimeSeconds "${SALifeTimeSeconds}" ` 41 | -MMSALifeTimeSeconds "${MMSALifeTimeSeconds}" ` 42 | -SADataSizeForRenegotiationKilobytes "${SADataSizeForRenegotiationKilobytes}" ` 43 | -PassThru 44 | Restart-Service RemoteAccess -PassThru 45 | } elseif ($ProductType -eq 1) { 46 | Write-Output "This seems to be a workstation. Configuring VPN client." 47 | if ($null -eq $ConnectionName) { 48 | Write-Output "Cannot configure a client without connection name. Please provide the ConnectionName argument." 49 | return 50 | } 51 | if ($Reset) { 52 | Write-Output "Resetting to default settings." 53 | Set-VpnConnectionIPsecConfiguration -ConnectionName "${ConnectionName}" -RevertToDefault -PassThru -Force 54 | } 55 | Set-VpnConnectionIPsecConfiguration ` 56 | -ConnectionName "${ConnectionName}" ` 57 | -AuthenticationTransformConstants "${AuthenticationTransformConstants}" ` 58 | -CipherTransformConstants "${CipherTransformConstants}" ` 59 | -DHGroup "${DHGroup}" ` 60 | -EncryptionMethod "${EncryptionMethod}" ` 61 | -IntegrityCheckMethod "${IntegrityCheckMethod}" ` 62 | -PfsGroup "${PfsGroup}" ` 63 | -PassThru ` 64 | -Force 65 | } else { 66 | Write-Output "Got unknown product type: ${ProductType}. Aborting." 67 | } 68 | Write-Output "VPN configuration ready." 69 | -------------------------------------------------------------------------------- /Setup-Syncthing.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Setup Syncthing using a standalone managed service account (sMSA) 4 | .LINK 5 | https://docs.syncthing.net/users/autostart.html#run-as-a-service-independent-of-user-login 6 | .LINK 7 | https://cybergladius.com/secure-windows-scheduled-tasks-with-managed-service-accounts/ 8 | #> 9 | 10 | # It may be possible to use psexec to run a command prompt as a service account 11 | # https://learn.microsoft.com/en-us/sysinternals/downloads/psexec 12 | 13 | $ErrorActionPreference = "Stop" 14 | 15 | . "${PSScriptRoot}\Utils.ps1" 16 | 17 | Install-Chocolatey 18 | choco install syncthing -y 19 | 20 | $Domain = Get-ADDomain 21 | $AccountName = "svc-$("${env:ComputerName}".ToLower())-sync" 22 | $AccountFullName = "$($Domain.NetBIOSName)\${AccountName}" 23 | Show-Output "Using account name: ${AccountName}" 24 | 25 | try { 26 | # Get-LocalUser "Syncthing" 27 | $Account = Get-ADServiceAccount -Identity "${AccountName}" 28 | } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { 29 | Show-Output "The Syncthing user was not found. Creating." 30 | 31 | # If you get the error "New-ADServiceAccount : Key does not exist" 32 | # Run this: 33 | # Add-KdsRootKey -EffectiveTime ((get-date).addhours(-10)) 34 | # https://learn.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/create-the-key-distribution-services-kds-root-key 35 | 36 | $Account = New-ADServiceAccount -Name "${AccountName}" -RestrictToSingleComputer 37 | } 38 | Show-Output "Installing the AD Service account" 39 | Install-ADServiceAccount $Account 40 | Test-ADServiceAccount $AccountName 41 | 42 | $SyncthingRoot = "${env:HomeDrive}\Syncthing" 43 | if (Test-Path "${SyncthingRoot}") { 44 | Show-Output "Syncthing folder exists at ${SyncthingRoot}" 45 | } else { 46 | Show-Output "Creating Syncthing folder at ${SyncthingRoot}" 47 | New-Item -Path "${SyncthingRoot}" -ItemType Directory 48 | } 49 | 50 | # https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-acl?view=powershell-7.4#example-5-grant-administrators-full-control-of-the-file 51 | $NewAcl = Get-Acl -Path "${SyncthingRoot}" 52 | $NTAccount = New-Object System.Security.Principal.NTAccount($Domain.NetBIOSName, "${AccountName}`$") 53 | $NewAcl.SetOwner($NTAccount) 54 | $AccessRule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule("${AccountName}`$", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow") 55 | $NewAcl.SetAccessRule($AccessRule) 56 | Set-Acl -Path "${SyncthingRoot}" -AclObject $NewAcl 57 | 58 | # $Credential = New-Object System.Management.Automation.PSCredential("${AccountName}`$", (New-Object System.Security.SecureString)) 59 | # $Credential = Get-Credential -UserName $AccountName 60 | # echo $AccountFullName 61 | # echo $Credential 62 | 63 | # try { 64 | # Get-Service "Syncthing" 65 | # sc.exe stop "Syncthing" 66 | # sc.exe delete "Syncthing" 67 | # Start-Sleep -Seconds 5 68 | # } catch {} 69 | 70 | # This results in error 1053 when starting 71 | # New-Service -Name "Syncthing" -BinaryPathName "${env:ProgramData}\chocolatey\bin\syncthing.exe --no-console --no-restart --no-browser --home=`"${SyncthingRoot}`"" 72 | 73 | # .\nssm.exe install "Syncthing" 74 | 75 | # Show-Output "You have to configure the account for the service manually. Go to services.msc -> Syncthing -> Properties -> Log On and use the account ${AccountFullName}`$ with an empty password." 76 | 77 | Get-ScheduledTask -TaskName "Syncthing" -ErrorAction SilentlyContinue -OutVariable Task 78 | if ($Task) { 79 | Show-Output "Unregistering old scheduled task" 80 | Unregister-ScheduledTask -TaskName "Syncthing" 81 | Start-Sleep -Seconds 1 82 | } 83 | 84 | $Trigger = New-ScheduledTaskTrigger -AtStartup 85 | $Action = New-ScheduledTaskAction -Execute "${env:ProgramData}\chocolatey\bin\syncthing.exe" -Argument "--no-console --no-browser --home=`"${SyncthingRoot}`"" 86 | $Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -ExecutionTimeLimit "00:00:00" # -Priority 8 87 | $Principal = New-ScheduledTaskPrincipal -UserId "${AccountFullName}`$" -LogonType Password -RunLevel Highest 88 | Register-ScheduledTask -TaskName "Syncthing" -Action $Action -Trigger $Trigger -Settings $Settings -Principal $Principal 89 | -------------------------------------------------------------------------------- /Run-PerfTest.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Test computer performance, stability and capabilities. 4 | Warning! The Phoronix Test Suite tests used by this script will require over 20 GB of disk space! 5 | .PARAMETER All 6 | Download all tests, even the manual ones. 7 | .PARAMETER DownloadOnly 8 | Only download the tests, but don't run them. 9 | .PARAMETER Geekbench 10 | Download Geekbench (manual due to licensing) 11 | .PARAMETER Unigine 12 | Download Unigine benchmarks (manual due to licensing) 13 | #> 14 | 15 | param( 16 | [switch]$All, 17 | [switch]$DownloadOnly, 18 | [switch]$Geekbench, 19 | [switch]$Unigine 20 | ) 21 | 22 | . "${PSScriptRoot}\Utils.ps1" 23 | Elevate($MyInvocation.MyCommand.Definition) 24 | 25 | Start-Transcript -Path "${LogPath}\perftest_$(Get-Date -Format "yyyy-MM-dd_HH-mm").txt" 26 | 27 | Show-Output "Running Mika's performance testing script" 28 | 29 | Install-PTS 30 | 31 | # The reporting has to be after PTS installation to be able to generate the PTS reports 32 | & ".\Report.ps1" 33 | # This folder is created by the reporting script 34 | $Reports = ".\reports" 35 | 36 | 37 | Show-Output "Installing dependencies" 38 | Install-Chocolatey 39 | 40 | Show-Output "Installing tests" 41 | choco upgrade -y speedtest 42 | if ($All -or $Unigine) { 43 | choco upgrade -y heaven-benchmark superposition-benchmark valley-benchmark 44 | } 45 | 46 | if (-not (Test-Path "${Downloads}\UserBenchMark.exe")) { 47 | Show-Output "Downloading UserBenchMark" 48 | Invoke-WebRequest -Uri "https://www.userbenchmark.com/resources/download/UserBenchMark.exe" -OutFile "${Downloads}\UserBenchMark.exe" 49 | } 50 | 51 | # Invoke-WebRequest -Uri "https://github.com/microsoft/diskspd/releases/download/v2.1/DiskSpd.ZIP" -OutFile "${Downloads}\DiskSpd.ZIP" 52 | # Expand-Archive -LiteralPath "${Downloads}\DiskSpd.ZIP" -DestinationPath "${Downloads}\DiskSpd" 53 | # https://github.com/ayavilevich/DiskSpdAuto 54 | 55 | if ($Geekbench -or $All) { 56 | Install-Geekbench 57 | } 58 | 59 | if ($DownloadOnly) { 60 | Show-Output "-DownloadOnly was specified. Everything is downloaded, so exiting now." 61 | exit 62 | } 63 | 64 | Show-Output "Configuring PTS" 65 | $Env:MONITOR="all" 66 | $Env:PERFORMANCE_PER_WATT="1" 67 | $PTS = "${Env:SystemDrive}\phoronix-test-suite\phoronix-test-suite.bat" 68 | & "$PTS" user-config-set AllowResultUploadsToOpenBenchmarking=TRUE 69 | & "$PTS" user-config-set AlwaysUploadSystemLogs=TRUE 70 | & "$PTS" user-config-set AlwaysUploadResultsToOpenBenchmarking=TRUE 71 | & "$PTS" user-config-set AnonymousUsageReporting=TRUE 72 | & "$PTS" user-config-set SaveSystemLogs=TRUE 73 | & "$PTS" user-config-set SaveResults=TRUE 74 | # & "$PTS" user-config-set PromptForTestDescription=FALSE 75 | & "$PTS" user-config-set PromptSaveName=FALSE 76 | 77 | Show-Output "Running PTS" 78 | & "$PTS" batch-benchmark ` 79 | pts/av1 ` 80 | pts/compiler ` 81 | pts/compression ` 82 | pts/cryptocurrency ` 83 | pts/cryptography ` 84 | pts/disk ` 85 | pts/hpc ` 86 | pts/machine-learning ` 87 | pts/memory ` 88 | pts/opencl ` 89 | pts/python ` 90 | pts/scientific-computing ` 91 | pts/video-encoding ` 92 | pts/cinebench ` 93 | pts/compress-7zip ` 94 | pts/encode-flac ` 95 | pts/intel-mlc ` 96 | pts/x264 97 | 98 | Show-Output "Running Speedtest" 99 | speedtest --accept-license --accept-gdpr --format=csv --output-header > "${Reports}\speedtest.csv" 100 | 101 | Show-Output "Running winsat disk test" 102 | winsat disk -drive C > "${Reports}\winsat_disk.txt" 103 | 104 | Show-Output "Running Windows performance monitoring." 105 | Show-Output "If it gets stuck, you can close its window." 106 | Start-Process -NoNewWindow -Wait perfmon /report 107 | 108 | # furmark /log_temperature /log_score 109 | 110 | # Running manual tests from a script is rather pointless. 111 | # 112 | # Waiting for a GUI program to finish 113 | # https://stackoverflow.com/a/7908022/ 114 | # 115 | # Start-Process -NoNewWindow -Wait ".\downloads\UserBenchMark.exe" 116 | # 117 | # Start-Process -NoNewWindow -Wait shell:appsFolder\MAXONComputerGmbH.Cinebench_rsne5bsk8s7tj!App 118 | # 119 | # # 3DMark and PCMark 10 have command-line support only in their professional versions. 120 | # # https://support.benchmarks.ul.com/support/solutions/articles/44002145411-run-3dmark-benchmarks-from-the-command-line 121 | # # https://support.benchmarks.ul.com/support/solutions/articles/44002182309-run-pcmark-10-benchmarks-from-the-command-line 122 | # $3DMarkPath = "${Env:ProgramFiles(x86)}\Steam\steamapps\common\3DMark\bin\x64\3DMark.exe" 123 | # if (Test-Path "$3DMarkPath") { 124 | # Start-Process -NoNewWindow -Wait "$3DMarkPath" 125 | # } 126 | # $PCMarkPath = "${Env:ProgramFiles(x86)}\Steam\steamapps\common\PCMark 10\bin\x64\PCMark10.exe" 127 | # if (Test-Path "$PCMarkPath") { 128 | # Start-Process -NoNewWindow -Wait $PCMarkPath 129 | # } 130 | 131 | Show-Output "The performance test is ready. You can now close this window." 132 | Stop-Transcript 133 | -------------------------------------------------------------------------------- /obs-studio/basic/scenes/Laptop_dock.json: -------------------------------------------------------------------------------- 1 | {"DesktopAudioDevice1":{"prev_ver":486604803,"name":"Desktop Audio","uuid":"451aba13-9eb5-4189-920c-3446e26e04fc","id":"wasapi_output_capture","versioned_id":"wasapi_output_capture","settings":{"device_id":"default"},"mixers":255,"sync":0,"flags":0,"volume":1.0,"balance":0.5,"enabled":true,"muted":false,"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"hotkeys":{"libobs.mute":[],"libobs.unmute":[],"libobs.push-to-mute":[],"libobs.push-to-talk":[]},"deinterlace_mode":0,"deinterlace_field_order":0,"monitoring_type":0,"private_settings":{}},"AuxAudioDevice1":{"prev_ver":486604803,"name":"Mic/Aux","uuid":"445ef3ff-b4db-4751-8a0d-96c4ab0e87a7","id":"wasapi_input_capture","versioned_id":"wasapi_input_capture","settings":{"device_id":"default"},"mixers":255,"sync":0,"flags":0,"volume":1.0,"balance":0.5,"enabled":true,"muted":false,"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"hotkeys":{"libobs.mute":[],"libobs.unmute":[],"libobs.push-to-mute":[],"libobs.push-to-talk":[]},"deinterlace_mode":0,"deinterlace_field_order":0,"monitoring_type":0,"private_settings":{}},"current_scene":"Left monitor","current_program_scene":"Left monitor","scene_order":[{"name":"Primary monitor"},{"name":"Left monitor"}],"name":"Laptop dock","sources":[{"prev_ver":486604803,"name":"Primary monitor","uuid":"98054bd7-3462-4363-b283-42d4030fbc37","id":"scene","versioned_id":"scene","settings":{"id_counter":1,"custom_size":false,"items":[{"name":"Primary monitor capture","source_uuid":"51d32cf0-6d3a-45be-bdee-e5c5dcf57841","visible":true,"locked":true,"rot":0.0,"pos":{"x":0.0,"y":138.0},"scale":{"x":0.55813956260681152,"y":0.55833333730697632},"align":5,"bounds_type":0,"bounds_align":0,"bounds":{"x":0.0,"y":0.0},"crop_left":0,"crop_top":0,"crop_right":0,"crop_bottom":0,"id":1,"group_item_backup":false,"scale_filter":"disable","blend_method":"default","blend_type":"normal","show_transition":{"duration":0},"hide_transition":{"duration":0},"private_settings":{}}]},"mixers":0,"sync":0,"flags":0,"volume":1.0,"balance":0.5,"enabled":true,"muted":false,"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"hotkeys":{"OBSBasic.SelectScene":[],"libobs.show_scene_item.Primary monitor capture":[],"libobs.hide_scene_item.Primary monitor capture":[]},"deinterlace_mode":0,"deinterlace_field_order":0,"monitoring_type":0,"private_settings":{}},{"prev_ver":486604803,"name":"Primary monitor capture","uuid":"51d32cf0-6d3a-45be-bdee-e5c5dcf57841","id":"monitor_capture","versioned_id":"monitor_capture","settings":{"monitor_id":"\\\\?\\DISPLAY#GSM7727#4&283e8cdb&0&UID41031#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"},"mixers":0,"sync":0,"flags":0,"volume":1.0,"balance":0.5,"enabled":true,"muted":false,"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"hotkeys":{},"deinterlace_mode":0,"deinterlace_field_order":0,"monitoring_type":0,"private_settings":{}},{"prev_ver":486604803,"name":"Left monitor","uuid":"fd0739f8-648f-4b49-b216-d1a7add53dab","id":"scene","versioned_id":"scene","settings":{"id_counter":1,"custom_size":false,"items":[{"name":"Left monitor capture","source_uuid":"2d4a73f8-a7b9-496d-8e9b-0885b307ce3c","visible":true,"locked":true,"rot":0.0,"pos":{"x":0.0,"y":0.0},"scale":{"x":1.0,"y":1.0},"align":5,"bounds_type":0,"bounds_align":0,"bounds":{"x":0.0,"y":0.0},"crop_left":0,"crop_top":0,"crop_right":0,"crop_bottom":0,"id":1,"group_item_backup":false,"scale_filter":"disable","blend_method":"default","blend_type":"normal","show_transition":{"duration":0},"hide_transition":{"duration":0},"private_settings":{}}]},"mixers":0,"sync":0,"flags":0,"volume":1.0,"balance":0.5,"enabled":true,"muted":false,"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"hotkeys":{"OBSBasic.SelectScene":[],"libobs.show_scene_item.Left monitor capture":[],"libobs.hide_scene_item.Left monitor capture":[]},"deinterlace_mode":0,"deinterlace_field_order":0,"monitoring_type":0,"private_settings":{}},{"prev_ver":486604803,"name":"Left monitor capture","uuid":"2d4a73f8-a7b9-496d-8e9b-0885b307ce3c","id":"monitor_capture","versioned_id":"monitor_capture","settings":{"monitor_id":"\\\\?\\DISPLAY#ACI24E1#4&283e8cdb&0&UID8261#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"},"mixers":0,"sync":0,"flags":0,"volume":1.0,"balance":0.5,"enabled":true,"muted":false,"push-to-mute":false,"push-to-mute-delay":0,"push-to-talk":false,"push-to-talk-delay":0,"hotkeys":{},"deinterlace_mode":0,"deinterlace_field_order":0,"monitoring_type":0,"private_settings":{}}],"groups":[],"quick_transitions":[{"name":"Cut","duration":300,"hotkeys":[],"id":1,"fade_to_black":false},{"name":"Fade","duration":300,"hotkeys":[],"id":2,"fade_to_black":false},{"name":"Fade","duration":300,"hotkeys":[],"id":3,"fade_to_black":true}],"transitions":[],"saved_projectors":[],"current_transition":"Fade","transition_duration":300,"preview_locked":false,"scaling_enabled":false,"scaling_level":0,"scaling_off_x":0.0,"scaling_off_y":0.0,"virtual-camera":{"type":0,"internal":0},"modules":{"scripts-tool":[],"output-timer":{"streamTimerHours":0,"streamTimerMinutes":0,"streamTimerSeconds":30,"recordTimerHours":0,"recordTimerMinutes":0,"recordTimerSeconds":30,"autoStartStreamTimer":false,"autoStartRecordTimer":false,"pauseRecordTimer":true},"auto-scene-switcher":{"interval":300,"non_matching_scene":"","switch_if_not_matching":false,"active":false,"switches":[]},"captions":{"source":"","enabled":false,"lang_id":1033,"provider":"mssapi"}}} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # windows-scripts 2 | A collection of Windows scripts I've found useful 3 | 4 | ## Installation instructions 5 | These installation instructions are for users that have not used command line before. 6 | If you are familiar with Git and PowerShell, you can clone and use the repository as you like instead of following these instructions. 7 | 8 | First check whether the scripts have already been installed on your computer. 9 | This can be done by checking whether there is a folder `Git\windows-scripts` in your home directory, 10 | or in the root of your C drive. 11 | The full path to this folder should be something like 12 | `C:\Users\\Git\windows-scripts` or `C:\Git\windows-scripts`. 13 | If this folder exists, move on to the [usage instructions](#usage-instructions). 14 | If it does not exist, please continue these installation instructions. 15 | 16 | To ease the setting up of these scripts on your computer, I have created an installation script. 17 | To download it, right-click 18 | [this link](https://raw.githubusercontent.com/AgenttiX/windows-scripts/master/Install-Repo.bat) 19 | and select "Save link as..." to save the file to a directory of your choice. 20 | Then right-click the downloaded file and select "Run as administrator". 21 | This should open a command-line window and setup the scripts and their dependencies for you. 22 | 23 | If you get a Windows SmartScreen error saying that the file is blocked, 24 | you have to unblock it by right-clicking the downloaded file and selecting Properties, 25 | and then checking the checkbox named Unblock at the bottom of the window. 26 | Then click OK and run the script again. 27 | 28 | Once the setup is complete, you can find the scripts in the directory 29 | `Git\windows-scripts` within your user folder (usually `C:\Users\`) if you chose per-user installation, 30 | or at `C:\Git\windows-scripts` if you chose global installation. 31 | 32 | ## Usage instructions 33 | Following the installation instructions should have created desktop icons for the installer and maintenance scripts. 34 | You can run these simply by double-clicking them. 35 | 36 | To run the other scripts, please follow these instructions. 37 | Most of these scripts are 38 | [PowerShell](https://en.wikipedia.org/wiki/PowerShell) 39 | scripts, which cannot be run by simply clicking them for security reasons. 40 | To run them, first open the `Git\windows-scripts` folder you have downloaded according to the installation instructions above in File Explorer ("resurssienhallinta" or "oma tietokone" in Finnish). 41 | Then press and hold shift, and right-click some empty space in the folder. 42 | In the context menu that opens you should see the option for "Open in Windows Terminal" (Windows 11) or "Open in PowerShell" (Windows 10). 43 | Select it. 44 | This should open a command prompt (a text-based window with a blinking cursor). 45 | If it's been a while since the scripts were downloaded, 46 | or if I've told you that there's a new version available, 47 | write `git pull` and press enter. 48 | This will download the latest updates to the scripts. 49 | 50 | Now in the command prompt you should write `.\ ` and the name of the script I have asked you to run. 51 | For example, the reporting script is selected with `.\Report.ps1`, the software installation script with `.\Install-Software.ps1` and the maintenance script with `.\Maintenance.ps1`. 52 | Then press enter to run the script. 53 | Many of these scripts have to be run as an administrator, and therefore they will request those privileges 54 | and then another window that operates with those privileges will open. 55 | If you get any errors, please send me a screenshot. 56 | 57 | Once the script has run, it may provide you with additional instructions such as how to send the results. 58 | Please follow those. 59 | 60 | ## Uninstalling managed software 61 | The installation script uses various package managers such as Chocolatey and Winget to manage the installed software. 62 | Therefore if you uninstall a managed program with its own uninstaller or from the list of installed applications in Windows settings, the program will be reinstalled with the next update. 63 | Therefore the program has to be uninstalled using the package manager. 64 | Please open an elevated PowerShell window as instructed above. 65 | For programs installed with Chocolatey the command is `choco uninstall ` (without the brackets). 66 | For programs installed with Winget the command is `winget uninstall --id `. 67 | Then press enter. 68 | You can find the names and IDs of the programs from the user interface of the installation script. 69 | 70 | ## Recreating virtualenvs after a Python upgrade 71 | If Python is installed through Chocolatey or some other package manager, it will be updated automatically. 72 | Major version upgrades will break existing virtualenvs. 73 | To recreate the virtualenv of a Python project, first delete the `venv` folder from the project directory. 74 | Then in PyCharm go to "File -> Settings -> Project -> Python Interpreter" and click on the gear symbol at the top-right. 75 | There select "Show all", select the old virtualenv and click the minus sign. 76 | Now the old virtualenv shold be deleted. 77 | Then click the plus sign and select "Virtualenv Environment -> New environment". 78 | The path should be the same as for the `venv` directory you just deleted. 79 | Then click OK on all the settings windows to close them. 80 | Now we can reinstall the project dependencies. 81 | Select "Terminal" from the bottom of the PyCharm window. 82 | This should open a terminal. 83 | However, it may not yet have the new virtualenv activated. 84 | Therefore write `exit` in the terminal to close it. 85 | Now open it again. 86 | Then write `pip -V` and press enter. 87 | This should show a path to the `venv` we just created. 88 | Now if the project has a `requirements.txt` file we can write `pip install -r requirements.txt` to install the dependencies. 89 | Otherwise we can install the dependencies manually, for example with `pip install matplotlib numpy`. 90 | The project should now be ready to use again. 91 | -------------------------------------------------------------------------------- /VPN/Create-VPNProfile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Create a connection profile for the Vexlum VPN using a computer that already has the VPN configured. 4 | .NOTES 5 | This script cannot be run over Remote Desktop or in a Hyper-V enhanced session. 6 | 7 | If you get the error "A general error occurred that is not covered by a more specific error code.", 8 | please see these links and the comments in SecuritySettings.ps1. 9 | https://github.com/MicrosoftDocs/windowsserverdocs/issues/6823 10 | https://www.reddit.com/r/sysadmin/comments/uyd4rg/aovpn_cimsession_enumerateinstances_fails_with_a/ 11 | https://directaccess.richardhicks.com/2022/02/07/always-on-vpn-powershell-script-issues-in-windows-11/ 12 | https://serverfault.com/questions/1131149/my-win-11-pro-vpn-client-for-ikev2-is-perpetually-broken 13 | .LINK 14 | https://docs.microsoft.com/en-us/windows-server/remote/remote-access/vpn/always-on-vpn/deploy/vpn-deploy-client-vpn-connections 15 | #> 16 | 17 | param( 18 | [Parameter(Mandatory=$true)][string]$TemplateName, 19 | [Parameter(Mandatory=$true)][string]$ProfileName, 20 | # VPN/RAS server, not NPS 21 | [Parameter(Mandatory=$true)][string]$Servers, 22 | [Parameter(Mandatory=$true)][string]$DNSSuffix, 23 | [Parameter(Mandatory=$true)][string]$DomainName, 24 | [Parameter(Mandatory=$true)][string]$DNSServers, 25 | [Parameter(Mandatory=$true)][string]$TrustedNetwork 26 | ) 27 | 28 | . "${PSScriptRoot}\SecuritySettings.ps1" 29 | 30 | # The $env:USERPROFILE does not work if OneDrive sync is enabled for the user folder 31 | # https://stackoverflow.com/a/64256803 32 | $DesktopPath = [Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop) 33 | # Write-Host $DesktopPath 34 | 35 | $Connection = Get-VpnConnection -Name $TemplateName 36 | if(!$Connection) 37 | { 38 | $Message = "Unable to get $TemplateName connection profile: $_" 39 | Write-Host "$Message" 40 | exit 41 | } 42 | $EAPSettings= $Connection.EapConfigXmlStream.InnerXml 43 | 44 | $ProfileXML = @(" 45 | 46 | ${DNSSuffix} 47 | 48 | ${Servers} 49 | IKEv2 50 | 51 | Eap 52 | 53 | 54 | ${EAPSettings} 55 | 56 | 57 | 58 | SplitTunnel 59 | 60 | ${AuthenticationTransformConstants} 61 | ${CipherTransformConstants} 62 | ${EncryptionMethod} 63 | ${IntegrityCheckMethod} 64 | ${DHGroup} 65 | ${PfsGroup} 66 | 67 | 68 | true 69 | true 70 | $TrustedNetwork 71 | 72 | $DomainName 73 | $DNSServers 74 | 75 | 76 | ") 77 | 78 | $ProfileXML | Out-File -FilePath "${DesktopPath}\VPN_Profile.xml" 79 | $Script = @(" 80 | <# 81 | .SYNOPSIS 82 | This is an automatically generated VPN configuration file. 83 | .LINK 84 | https://docs.microsoft.com/en-us/windows-server/remote/remote-access/vpn/always-on-vpn/deploy/vpn-deploy-client-vpn-connections 85 | #> 86 | 87 | `$ProfileName = '$ProfileName' 88 | `$ProfileNameEscaped = `$ProfileName -replace ' ', '%20' 89 | 90 | `$ProfileXML = '$ProfileXML' 91 | 92 | `$ProfileXML = `$ProfileXML -replace '<', '<' 93 | `$ProfileXML = `$ProfileXML -replace '>', '>' 94 | `$ProfileXML = `$ProfileXML -replace '`"', '"' 95 | 96 | `$nodeCSPURI = `"./Vendor/MSFT/VPNv2`" 97 | `$namespaceName = `"root\cimv2\mdm\dmmap`" 98 | `$className = `"MDM_VPNv2_01`" 99 | 100 | try 101 | { 102 | `$username = Gwmi -Class Win32_ComputerSystem | select username 103 | `$objuser = New-Object System.Security.Principal.NTAccount(`$username.username) 104 | `$sid = `$objuser.Translate([System.Security.Principal.SecurityIdentifier]) 105 | `$SidValue = `$sid.Value 106 | `$Message = `"User SID is `$SidValue.`" 107 | Write-Host `"`$Message`" 108 | } 109 | catch [Exception] 110 | { 111 | `$Message = `"Unable to get user SID. User may be logged on over Remote Desktop: `$_`" 112 | Write-Host `"`$Message`" 113 | exit 114 | } 115 | 116 | `$session = New-CimSession 117 | `$options = New-Object Microsoft.Management.Infrastructure.Options.CimOperationOptions 118 | `$options.SetCustomOption(`"PolicyPlatformContext_PrincipalContext_Type`", `"PolicyPlatform_UserContext`", `$false) 119 | `$options.SetCustomOption(`"PolicyPlatformContext_PrincipalContext_Id`", `"`$SidValue`", `$false) 120 | 121 | try 122 | { 123 | `$deleteInstances = `$session.EnumerateInstances(`$namespaceName, `$className, `$options) 124 | foreach (`$deleteInstance in `$deleteInstances) 125 | { 126 | `$InstanceId = `$deleteInstance.InstanceID 127 | if (`"`$InstanceId`" -eq `"`$ProfileNameEscaped`") 128 | { 129 | `$session.DeleteInstance(`$namespaceName, `$deleteInstance, `$options) 130 | `$Message = `"Removed `$ProfileName profile `$InstanceId`" 131 | Write-Host `"`$Message`" 132 | } else { 133 | `$Message = `"Ignoring existing VPN profile `$InstanceId`" 134 | Write-Host `"`$Message`" 135 | } 136 | } 137 | } 138 | catch [Exception] 139 | { 140 | `$Message = `"Unable to remove existing outdated instance(s) of `$ProfileName profile: `$_`" 141 | Write-Host `"`$Message`" 142 | exit 143 | } 144 | 145 | try 146 | { 147 | `$newInstance = New-Object Microsoft.Management.Infrastructure.CimInstance `$className, `$namespaceName 148 | `$property = [Microsoft.Management.Infrastructure.CimProperty]::Create(`"ParentID`", `"`$nodeCSPURI`", `"String`", `"Key`") 149 | `$newInstance.CimInstanceProperties.Add(`$property) 150 | `$property = [Microsoft.Management.Infrastructure.CimProperty]::Create(`"InstanceID`", `"`$ProfileNameEscaped`", `"String`", `"Key`") 151 | `$newInstance.CimInstanceProperties.Add(`$property) 152 | `$property = [Microsoft.Management.Infrastructure.CimProperty]::Create(`"ProfileXML`", `"`$ProfileXML`", `"String`", `"Property`") 153 | `$newInstance.CimInstanceProperties.Add(`$property) 154 | `$session.CreateInstance(`$namespaceName, `$newInstance, `$options) 155 | `$Message = `"Created `$ProfileName profile.`" 156 | 157 | Write-Host `"`$Message`" 158 | } 159 | catch [Exception] 160 | { 161 | `$Message = `"Unable to create `$ProfileName profile: `$_`" 162 | Write-Host `"`$Message`" 163 | exit 164 | } 165 | 166 | `$Message = `"Script Complete`" 167 | Write-Host `"`$Message`" 168 | ") 169 | 170 | $ScriptPath = "${DesktopPath}\VPN_Profile.ps1" 171 | $Script | Out-File -FilePath "${ScriptPath}" 172 | . "$(Split-Path -Parent ${PSScriptRoot})\Sign-Script.ps1" -FilePath "${ScriptPath}" 173 | 174 | $Message = "Successfully created VPN_Profile.xml and VPN_Profile.ps1 on the desktop." 175 | Write-Host "$Message" 176 | -------------------------------------------------------------------------------- /Report.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Create reports on computer status and health 4 | .PARAMETER NoArchive 5 | Do not generate the zip archive. This is useful if you want to generate additional reports after this script. 6 | .PARAMETER OnlyArchive 7 | Only create the archive from existing reports. This is useful if you have generated additional reports after this script. 8 | #> 9 | 10 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Elevated", Justification="Used in utils")] 11 | param( 12 | [switch]$Elevated, 13 | [switch]$NoArchive, 14 | [switch]$OnlyArchive 15 | ) 16 | 17 | . "${PSScriptRoot}\Utils.ps1" 18 | 19 | if ($RepoInUserDir) { 20 | Update-Repo 21 | } 22 | Elevate($myinvocation.MyCommand.Definition) 23 | if (! $RepoInUserDir) { 24 | Update-Repo 25 | } 26 | 27 | $host.ui.RawUI.WindowTitle = "Mika's reporting script" 28 | 29 | # These variables are defined already here so that they are available also 30 | # when only running the Compress-ReportArchive below. 31 | # $Downloads = ".\Downloads" 32 | $Reports = "${PSScriptRoot}\Reports" 33 | $Timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm" 34 | 35 | function Compress-ReportArchive { 36 | Show-Output "Creating the report archive." 37 | Compress-Archive -Path "${Reports}" -DestinationPath "${DesktopPath}\IT_report_${Timestamp}.zip" -CompressionLevel Optimal 38 | } 39 | 40 | if ($OnlyArchive) { 41 | Compress-ReportArchive 42 | exit 43 | } 44 | 45 | # ----- 46 | # Initialization 47 | # ----- 48 | Show-Output "Running Mika's reporting script." 49 | New-Item -Path "." -Name "Reports" -ItemType "directory" -Force | Out-Null 50 | 51 | Show-Output "Removing old reports." 52 | Get-ChildItem "${Reports}/*" -Recurse | Remove-Item 53 | 54 | Show-Output "Adding a README to the report." 55 | (Get-Content "${PSScriptRoot}/Report-Readme-Template.txt").Replace("HOST", "${env:ComputerName}").Replace("TIMESTAMP", "${Timestamp}") | Set-Content "${Reports}\README.txt" 56 | 57 | # ----- 58 | # Getter commands (in alphabetical order) 59 | # ----- 60 | Show-Output "Creating report of installed Windows Store apps." 61 | Get-AppxPackage > "${Reports}\appx_packages.txt" 62 | 63 | Show-Output "Checking Windows Experience Index." 64 | Get-CimInstance Win32_WinSat > "${Reports}\windows_experience_index.txt" 65 | 66 | Show-Output "Creating report of basic computer info." 67 | Get-ComputerInfo > "${Reports}\computer_info.txt" 68 | 69 | Show-Output "Creating report of SSD/HDD SMART data." 70 | Get-Disk | Get-StorageReliabilityCounter | Select-Object -Property "*" > "${Reports}\smart.txt" 71 | 72 | Show-Output "Creating report of network configuration." 73 | Get-NetIPConfiguration | Select-Object ` 74 | "InterfaceAlias", 75 | "InterfaceDescription", 76 | @{n="MacAddress"; e={$_.NetAdapter.MacAddress}}, 77 | @{n="MacAddressColon"; e={$_.NetAdapter.MacAddress.Replace("-", ":")}}, 78 | @{n="IPv4Address"; e={$_.IPv4Address -join ", "}}, 79 | @{n="IPv6Address"; e={$_.IPv6Address -join ", "}}, 80 | @{n="IPv4DefaultGateway"; e={$_.IPv4DefaultGateway.NextHop -join ", "}}, 81 | @{n="IPv6DefaultGateway"; e={$_.IPv6DefaultGateway.NextHop -join ", "}}, 82 | @{n="DNSServer"; e={$_.DNSServer.ServerAddresses -join ", "}} > "${Reports}\network_configuration.txt" 83 | 84 | Show-Output "Creating report of Plug and Play devices." 85 | Get-PnPDevice > "${Reports}\pnp_devices.txt" 86 | 87 | Show-Output "Extracting Windows Update logs." 88 | Get-WindowsUpdateLog -LogPath "${Reports}\WindowsUpdate.log" 89 | 90 | # ----- 91 | # External commands (in alphabetical order) 92 | # ----- 93 | if (Test-CommandExists "choco") { 94 | Show-Output "Creating report of installed Chocolatey apps." 95 | choco --local > "${Reports}\choco.txt" 96 | } else { 97 | Show-Output "The command `"choco`" was not found." 98 | } 99 | 100 | if (Test-CommandExists "dxdiag") { 101 | Show-Output "Creating DirectX reports." 102 | dxdiag /x "${Reports}\dxdiag.xml" 103 | dxdiag /t "${Reports}\dxdiag.txt" 104 | dxdiag /x "${Reports}\dxdiag-whql.xml" /whql:on 105 | dxdiag /t "${Reports}\dxdiag-whql.txt" /whql:on 106 | } else { 107 | Show-Output "The command `"dxdiag`" was not found." 108 | } 109 | 110 | if (Test-CommandExists "gpresult") { 111 | Show-Output "Creating report of group policies." 112 | gpresult /h "${Reports}\gpresult.html" /f 113 | } else { 114 | Show-Output "The command `"gpresult`" was not found." 115 | } 116 | 117 | if (Test-CommandExists "manage-bde") { 118 | manage-bde -status > "${Reports}\manage-bde.txt" 119 | } else { 120 | Show-Output "The command `"manage-bde`" was not found." 121 | } 122 | 123 | if (Test-CommandExists "netsh") { 124 | Show-Output "Creating WLAN report." 125 | netsh wlan show wlanreport 126 | $WlanReportPath1 = "C:\ProgramData\Microsoft\Windows\WlanReport\wlan-report-latest.html" 127 | $WlanReportPath2 = "C:\ProgramData\Microsoft\Windows\WlanReport\wlan_report_latest.html" 128 | if (Test-Path "${WlanReportPath1}") { 129 | Copy-Item "${WlanReportPath1}" "${Reports}" 130 | } elseif (Test-path "${WlanReportPath2}") { 131 | Copy-Item "${WlanReportPath2}" "${Reports}" 132 | } else { 133 | Show-Output -ForegroundColor Red "The WLAN report was not found." 134 | } 135 | } else { 136 | Show-Output "The command `"netsh`" was not found." 137 | } 138 | 139 | $OpenVPNLogs = "${UserDir}\OpenVPN\log" 140 | if (Test-Path "${OpenVPNLogs}") { 141 | Show-Output "OpenVPN log folder found. Copying logs." 142 | New-Item -Path "${Reports}" -Name "OpenVPN" -ItemType "directory" -Force | Out-Null 143 | Copy-Item -Path "${OpenVPNLogs}\*" -Destination "${Reports}\OpenVPN" 144 | } else { 145 | Show-Output "OpenVPN log folder was not found." 146 | } 147 | 148 | if (Test-CommandExists "powercfg") { 149 | Show-Output "Creating battery report." 150 | powercfg /availablesleepstates > "${Reports}\powercfg_sleepstates.html" 151 | powercfg /batteryreport /output "${Reports}\powercfg_battery.html" 152 | powercfg /devicequery wake_armed > "${Reports}\powercfg_devicequery_wake_armed.txt" 153 | powercfg /energy /output "${Reports}\powercfg_energy.html" 154 | powercfg /getactivescheme > "${Reports}\powercfg_activescheme.txt" 155 | powercfg /lastwake > "${Reports}\powercfg_lastwake.txt" 156 | powercfg /list > "${Reports}\powercfg_list.txt" 157 | powercfg /provisioningxml /output "${Reports}\powercfg_provisioning.xml" 158 | powercfg /sleepstudy /output "${Reports}\powercfg_sleepstudy.html" 159 | powercfg /srumutil /output "${Reports}\powercfg_srumutil.csv" /csv 160 | powercfg /systempowerreport /output "${Reports}\powercfg_systempowerreport.html" 161 | powercfg /waketimers > "${Reports}\powercfg_waketimers.txt" 162 | } else { 163 | Show-Output "The command `"powercfg`" was not found." 164 | } 165 | 166 | # ----- 167 | # Complex external programs 168 | # ----- 169 | $PTS = "${Env:SystemDrive}\phoronix-test-suite\phoronix-test-suite.bat" 170 | if (Test-Path $PTS) { 171 | Show-Output "Creating Phoronix Test Suite (PTS) reports" 172 | & "$PTS" diagnostics > "${Reports}\pts_diagnostics.txt" 173 | & "$PTS" system-info > "${Reports}\pts_system_info.txt" 174 | & "$PTS" system-properties > "${Reports}\pts_system_properties.txt" 175 | & "$PTS" system-sensors > "${Reports}\pts_system_sensors.txt" 176 | & "$PTS" network-info > "${Reports}\pts_network_info.txt" 177 | } else { 178 | Show-Output "Phoronix Test Suite (PTS) was not found." 179 | } 180 | 181 | # ----- 182 | # Packaging 183 | # ----- 184 | if (-not $NoArchive) { 185 | Compress-ReportArchive 186 | Show-Output "The reporting script is ready." -ForegroundColor Green 187 | Show-Output "The reports can be found in the zip file on your desktop, and at `"${RepoPath}\Reports`"." -ForegroundColor Green 188 | Show-Output "If Mika requested you to run this script, please send the zip file from your desktop to him." -ForegroundColor Green 189 | Show-Output "You can close this window now." -ForegroundColor Green 190 | } 191 | -------------------------------------------------------------------------------- /Maintenance.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Maintenance script for Windows-based computers 4 | .PARAMETER Clean 5 | Clean the system by removing old temporary files etc. 6 | .PARAMETER Deep 7 | Run a deep cleanup. This will take longer. 8 | .PARAMETER Docker 9 | Perform a deep clean of Docker. This will clean all unused images. 10 | .PARAMETER Elevated 11 | This parameter is for internal use to check whether an UAC prompt has already been attempted 12 | .PARAMETER Firefox 13 | Remove Firefox cookies etc. 14 | .PARAMETER Reboot 15 | Reboot the computer when the script is ready. 16 | .PARAMETER Shutdown 17 | Shut the computer down when the script is ready. 18 | .PARAMETER Thunderbird 19 | Remove local Thunderbird IMAP folders etc. 20 | #> 21 | 22 | param( 23 | [switch]$Clean, 24 | [switch]$Deep, 25 | [switch]$Docker, 26 | [switch]$Elevated, 27 | [switch]$Firefox, 28 | [switch]$Reboot, 29 | [switch]$Scheduled, 30 | [switch]$SetupOnly, 31 | [switch]$Shutdown, 32 | [switch]$Thunderbird, 33 | [switch]$Zerofree 34 | ) 35 | 36 | # Load utility functions from another file. 37 | . "${PSScriptRoot}\Utils.ps1" 38 | 39 | if ($Reboot -and $Shutdown) { 40 | # The Show-Output function is defined in Utils.ps1 41 | Show-Output -ForegroundColor Red "Both reboot and shutdown switches cannot be enabled at the same time." 42 | Exit 1 43 | } 44 | 45 | $TimestampPath = "${LogPath}\previous_maintenance_timestamp.txt" 46 | if ($Scheduled) { 47 | $PreviousRunDate = [Datetime](Get-Content $TimestampPath) 48 | $TimeDifference = New-TimeSpan -Start $PreviousRunDate -End (Get-Date) 49 | $MaxTimeSpan = New-TimeSpan -Days 30 50 | if ($TimeDifference -lt $MaxTimeSpan) { 51 | Show-Output "Skipping maintenance, as a sufficient time has not passed since the previous run, which was on ${PreviousRunDate}" 52 | Exit 53 | } 54 | if (! (Get-YesNo "Do you want to run maintenance? Previous maintenance was run on ${PreviousRunDate}, which was ${TimeDifference} ago.")) { 55 | Exit 56 | } 57 | } 58 | 59 | # Scheduled task setup 60 | # (Should be run before elevation to ensure, that the task starts with lower permissions) 61 | if ($Elevated) { 62 | Show-Output "Already elevated, skipping scheduled task setup." 63 | } elseif ($RepoInUserDir) { 64 | if ($SetupOnly) { 65 | Show-Output "Cannot create scheduled task, since the repo is within the user directory." 66 | } 67 | Show-Output "The repo is within the user directory. Skipping scheduled task creation." 68 | } else { 69 | $TaskName = "Maintenance" 70 | # $Description = "Mika's maintenance script" 71 | $Action = New-ScheduledTaskAction -Execute "powershell" -Argument "-File ${PSCommandPath} -Scheduled" 72 | $Trigger = New-ScheduledTaskTrigger -Daily -At 10am -RandomDelay (New-TimeSpan -Hours 4) 73 | $TaskExists = Get-ScheduledTask | Where-Object {$_.TaskName -like $TaskName } 74 | if ($TaskExists) { 75 | $Task = Get-ScheduledTask -TaskName $TaskName 76 | Set-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger | Out-Null 77 | } else { 78 | $Task = New-ScheduledTask -Action $Action -Trigger $Trigger 79 | Register-ScheduledTask -TaskName "Maintenance" -InputObject $Task | Out-Null 80 | } 81 | 82 | # Unregister-ScheduledTask -Taskname "Maintenance" 83 | 84 | if ($SetupOnly) { 85 | Show-Output "Scheduled maintenance task created." 86 | } 87 | } 88 | 89 | if ($RepoInUserDir) { 90 | Update-Repo 91 | } 92 | Elevate($myinvocation.MyCommand.Definition) 93 | # The window title should be set after elevation so that the original launching window doesn't get the title. 94 | $host.ui.RawUI.WindowTitle = "Mika's maintenance script" 95 | 96 | # If the log path is not writable without elevation, this has to be after the elevation. 97 | Start-Transcript -Path "${LogPath}\maintenance_$(Get-Date -Format "yyyy-MM-dd_HH-mm").txt" 98 | 99 | if (! $RepoInUserDir) { 100 | Update-Repo 101 | } 102 | 103 | # These have to be run after elevation 104 | if ($SetupOnly) { 105 | Add-ScriptShortcuts 106 | Set-RepoPermissions 107 | 108 | Stop-Transcript 109 | Exit 110 | } 111 | 112 | # Startup info 113 | Show-Output -ForegroundColor Cyan "Starting Mika's maintenance script." 114 | Show-Output -ForegroundColor Cyan "If some updater requests a reboot, select no, and only reboot the computer when the installation script is ready." 115 | Request-DomainConnection 116 | 117 | # --- 118 | # Constants 119 | # --- 120 | 121 | # The list of cleaners can be obtained with the parameter --list-cleaners 122 | $bleachbit_features = @( 123 | "adobe_reader.*", 124 | # "amsn.*", 125 | "amule.*", 126 | # "apt.*", 127 | # "audacious.*", 128 | # "bash.*", 129 | # "beagle.*", 130 | "chromium.*", 131 | # "chromium.cache", 132 | # "chromium.cookies", 133 | # "chromium.current_session", 134 | # "chromium.dom", 135 | # "chromium.form_history", 136 | # "chromium.history", 137 | # "chromium.passwords", 138 | # "chromium.search_engines", 139 | # "chromium.vacuum" 140 | # "d4x.*", 141 | # "deepscan.backup", 142 | # "deepscan.ds_store", 143 | # "deepscan.thumbs_db", 144 | # "deepscan.tmp", 145 | # "easytag.*", 146 | # "elinks.*", 147 | # "emesene.*", 148 | # "epiphany.*", 149 | # "evolution.*", 150 | # "exaile.*", 151 | "filezilla.*", 152 | # "firefox.*", 153 | "firefox.backup", 154 | "firefox.cache", 155 | # "firefox.cookies", 156 | "firefox.crash_reports", 157 | "firefox.dom", 158 | # "firefox.download_history", 159 | "firefox.forms", 160 | # "firefox.passwords", 161 | "firefox.session_restore", 162 | "firefox.site_preferences", 163 | "firefox.url_history", 164 | "firefox.vacuum", 165 | "flash.*", 166 | # "gedit.*", 167 | # "gftp.*", 168 | "gimp.*", 169 | # "gl-117.*", 170 | # "gnome.*", 171 | "google_chrome.*", 172 | "google_earth.*", 173 | "google_toolbar.*", 174 | "gpodder.*", 175 | # "gwenview.*", 176 | "hexchat.*", 177 | "hippo_opensim_viewer.*", 178 | "internet_explorer.*", 179 | "java.*", 180 | # "kde.*", 181 | # "konqueror.*", 182 | "libreoffice.*", 183 | # "liferea.*", 184 | # "links2.*", 185 | "microsoft_office.*", 186 | "midnightcommander.*", 187 | "miro.*", 188 | # "nautilus.*", 189 | # "nexuiz.*", 190 | "octave.*", 191 | "openofficeorg.*", 192 | "opera.*", 193 | "paint.*", 194 | "pidgin.*", 195 | "realplayer.*", 196 | # "recoll.*", 197 | # "rhythmbox.*", 198 | "safari.*", 199 | "screenlets.*", 200 | "seamonkey.*", 201 | "secondlife_viewer.*", 202 | "silverlight.*", 203 | "skype.*", 204 | "smartftp.*", 205 | # "sqlite3.*", 206 | # "system.cache", 207 | "system.clipboard", 208 | # "system.custom", 209 | # "system.desktop_entry", 210 | # "system.free_disk_space", 211 | # "system.localizations", 212 | "system.logs", 213 | # "system.memory", 214 | "system.memory_dump", 215 | "system.muicache", 216 | "system.prefetch", 217 | # "system.recent_documents", 218 | "system.recycle_bin", 219 | # "system.rotated_logs", 220 | "system.tmp", 221 | # "system.trash", 222 | "system.updates", 223 | "teamviewer.*", 224 | # "thumbnails.*", 225 | # "thunderbird.cache", 226 | "thunderbird.cookies", 227 | # "thunderbird.index", 228 | # "thunderbird.passwords", 229 | "thunderbird.vacuum", 230 | "tortoisesvn.*", 231 | # "transmission.blocklists", 232 | # "transmission.history", 233 | # "transmission.torrents", 234 | # "tremulous.*", 235 | "vim.*", 236 | "vlc.*", 237 | "vuze.*", 238 | "warzone2100.*", 239 | "waterfox.*", 240 | "winamp.*", 241 | "windows_defender.*", 242 | # "wine.*", 243 | # "winetricks.*", 244 | "winrar.*", 245 | "winzip.*", 246 | "wordpad.*", 247 | # "x11.*", 248 | # "xine.*", 249 | "yahoo_messenger.*" 250 | # "yum.*" 251 | ) 252 | 253 | $bleachbit_features_deep = @( 254 | "deepscan.backup", 255 | "deepscan.ds_store", 256 | "deepscan.thumbs_db", 257 | "deepscan.tmp" 258 | ) 259 | 260 | $bleachbit_features_firefox = @( 261 | "firefox.*" 262 | ) 263 | 264 | $bleachbit_features_thunderbird = @( 265 | "thunderbird.cache", 266 | "thunderbird.index" 267 | ) 268 | 269 | # --- 270 | # Script starts here 271 | # --- 272 | 273 | Test-PendingRebootAndExit 274 | 275 | if ((-not $Zerofree) -and (Get-IsVirtualBoxMachine)) { 276 | Show-Output -ForegroundColor Cyan "This seems to be a VirtualBox machine." 277 | $Zerofree = Get-YesNo "Do you want to zero free space at the end of this script?" 278 | } 279 | if ($Zerofree) { 280 | Show-Output -ForegroundColor Cyan "Free space will be zeroed at the end of this script." 281 | } 282 | 283 | # Check that SSH keys are stored on secure devices 284 | $SSHDir = "${UserDir}\.ssh" 285 | if ((Test-Path "${SSHDir}") -and (Test-CommandExists "ssh-keygen")) { 286 | Show-Output "SSH configuration folder found. Checking that the keys are secure." 287 | $BadKeys = [System.Collections.ArrayList]@() 288 | # Get-ChildItem does not support -Filter and -Exclude at the same time 289 | Get-ChildItem "${SSHDir}" -Filter "id_*" | Foreach-Object { 290 | # Skip .pub files 291 | if ($_.FullName -match '^(?!.*\.pub$).*$') { 292 | $KeyProps = "$(ssh-keygen -lf $_.FullName)" 293 | # Get the last part and strip first and last characters 294 | $KeyType = $KeyProps.split(" ")[-1] -replace "^." -replace ".$" 295 | if (-not $KeyType.EndsWith("-SK")) { 296 | $BadKeys.Add("$($_.FullName)`n${KeyProps}") 297 | } 298 | } 299 | } 300 | if ($BadKeys.Length) { 301 | Add-Type -AssemblyName PresentationCore,PresentationFramework 302 | $ButtonType = [System.Windows.MessageBoxButton]::OK 303 | $MessageIcon = [System.Windows.MessageBoxImage]::Error 304 | $MessageBody = ( 305 | "The following SSH keys seem not to be stored on a secure device " + 306 | "such as a TPM (Windows Hello) or a FIDO2 hardware token (e.g. YubiKey). " + 307 | "Please replace them with secure keys as soon as possible. " + 308 | "Please see https://agx.fi/it/ssh for further instructions.`n`n" + ($BadKeys -join "`n`n") 309 | ) 310 | $MessageTitle = "Insecure SSH keys detected" 311 | [System.Windows.MessageBox]::Show($MessageBody, $MessageTitle, $ButtonType, $MessageIcon) 312 | Start-Process "https://agx.fi/it/ssh" 313 | } 314 | } else { 315 | Show-Output "SSH configuration folder or ssh-keygen was not found." 316 | } 317 | 318 | # Loaded globally, since these are slow 319 | $ComputerInfo = Get-ComputerInfo 320 | $IsDomainJoined = Get-IsDomainJoined 321 | 322 | # If a Lenovo computer does not have Lenovo Vantage installed 323 | $IsLenovoComputer = $ComputerInfo.BiosManufacturer.ToLower() -eq "lenovo" 324 | $LenovoVantageInstalled = Get-AppxPackage -Name "E046963F.LenovoCompanion" 325 | $LenovoCommercialVantageInstalled = Get-AppxPackage -Name "E046963F.LenovoSettingsforEnterprise" 326 | if ($IsLenovoComputer -and (! ($LenovoVantageInstalled -or $LenovoCommercialVantageInstalled))) { 327 | Show-Output -ForegroundColor Red "It appears that you have a Lenovo computer but don't have Lenovo Vantage or Lenovo Commercial Vantage installed." 328 | if ($IsDomainJoined) { 329 | Show-Output -ForegroundColor Red "Your computer appears to be part of a domain. Please install Lenovo Commercial Vantage to get driver and firmware updates." 330 | Start-Process "https://apps.microsoft.com/store/detail/lenovo-commercial-vantage/9NR5B8GVVM13" 331 | } else { 332 | Show-Output -ForegroundColor Red "Please install Lenovo Vantage from Microsoft Store to get driver and firmware updates." 333 | Start-Process "https://apps.microsoft.com/store/detail/lenovo-vantage/9WZDNCRFJ4MV" 334 | } 335 | } 336 | 337 | if ($Reboot) { 338 | Show-Output -ForegroundColor Cyan "The computer will be rebooted automatically after the script is complete due to a command-line argument." 339 | } elseif ($Shutdown) { 340 | Show-Output -ForegroundColor Cyan "The computer will be shut down automatically after the script is complete due to a command-line argument." 341 | } 342 | # else { 343 | # Show-Output "Do you want to reboot or shut down automatically after the script is complete?" 344 | # Show-Output "Do not enable these if you have large game updates to download, as those may not finish." 345 | # $reply = Read-Host -Prompt "[r/s/n]?" 346 | # if ($reply -match "[rR]") { 347 | # $Reboot = $true 348 | # Show-Output "Automatic reboot has been enabled." 349 | # } elseif ($reply -match "[sS]" ) { 350 | # $Shutdown = $true 351 | # Show-Output "Automatic shutdown has been enabled." 352 | # } else { 353 | # $Reboot = $false 354 | # $Shutdown = $false 355 | # Show-Output "Automatic reboot and shutdown are disabled." 356 | # } 357 | # } 358 | 359 | # --- 360 | # Actual operations start here 361 | # --- 362 | 363 | Show-Output -ForegroundColor Cyan "Performing initial steps that have to be performed before Windows Update." 364 | Show-Output -ForegroundColor Red "Do not write in the console or press enter unless requested." 365 | Show-Output -ForegroundColor Cyan "After a moment you may be asked about Windows Updates, and writing in the console now may cause in the selection of updates you don't want." 366 | 367 | # Resynchronize time with domain controllers or other NTP server. 368 | # This may be needed for gpupdate if the internal clock is out of sync with the domain. 369 | Show-Output -ForegroundColor Cyan "Synchronizing system clock. If your computer is part of a domain but not connected to the domain network (e.g. with a VPN), this may fail." 370 | w32tm /resync 371 | 372 | if (Test-CommandExists "gpupdate") { 373 | Show-Output -ForegroundColor Cyan "Updating group policies. If your computer is part of a domain but not connected to the domain network (e.g. with a VPN), this will fail." 374 | Show-Output -ForegroundColor Cyan "`"Failed to apply`" error messages are also quite common, and may be caused by reasons unrelated to your computer (synchronization problems for domain controller SYSVOL etc.)." 375 | gpupdate /force 376 | } else { 377 | Show-Output -ForegroundColor Cyan "Group policy updates are not supported on this system." 378 | } 379 | 380 | if (Test-CommandExists "Install-Module") { 381 | Show-Output -ForegroundColor Cyan "Installing PowerShell bindings for Windows Update. You may now be asked whether to install the NuGet package provider. Please select yes." 382 | Install-Module PSWindowsUpdate -Force 383 | } else { 384 | Show-Output -ForegroundColor Red "Windows Update PowerShell module could not be installed. Check Windows updates manually." 385 | } 386 | Import-Module PSWindowsUpdate 387 | if (Test-CommandExists "Install-WindowsUpdate") { 388 | Show-Output -ForegroundColor Cyan "You may now be asked whether to install some Windows Updates." 389 | Show-Output -ForegroundColor Cyan "It's recommended to answer yes EXCEPT for the following:" 390 | Show-Output -ForegroundColor Cyan "- Microsoft Silverlight" 391 | Show-Output -ForegroundColor Cyan "- Preview versions" 392 | Install-WindowsUpdate -MicrosoftUpdate -IgnoreReboot 393 | } else { 394 | Show-Output -ForegroundColor Red "Windows Update bindings were not found. You have to check for Windows updates manually." 395 | } 396 | 397 | Install-Chocolatey 398 | if (Test-CommandExists "choco") { 399 | $ChocoPackages = choco list 400 | if ($ChocoPackages -match "altdrag .*") { 401 | Show-Output -ForegroundColor Cyan "Replacing discontinued AltDrag with AltSnap" 402 | choco uninstall altdrag -y 403 | choco install altsnap -y 404 | } 405 | Show-Output -ForegroundColor Cyan "Installing updates with Chocolatey" 406 | choco upgrade all -y 407 | } 408 | 409 | if (Test-CommandExists "winget") { 410 | Show-Output -ForegroundColor Cyan "Installing updates with Winget. If you are asked to agree to source agreements terms, please select yes." 411 | winget upgrade --all 412 | } 413 | 414 | # BleachBit 415 | $bleachbit_path_native = "${env:ProgramFiles}\BleachBit\bleachbit_console.exe" 416 | $bleachbit_path_x86 = "${env:ProgramFiles(x86)}\BleachBit\bleachbit_console.exe" 417 | 418 | if ($Clean -or $Deep) { 419 | if ((-not ((Test-Path $bleachbit_path_native) -or (Test-Path $bleachbit_path_x86))) -and (Test-CommandExists "choco")) { 420 | choco install bleachbit -y 421 | } 422 | if ((Test-Path $bleachbit_path_native) -or (Test-Path $bleachbit_path_x86)) { 423 | $bleachbit_cleaners = $bleachbit_features 424 | if ($Deep) {$bleachbit_cleaners += $bleachbit_features_deep} 425 | if ($Firefox) {$bleachbit_cleaners += $bleachbit_features_firefox} 426 | if ($Thunderbird) {$bleachbit_cleaners += $bleachbit_features_thunderbird} 427 | Show-Output -ForegroundColor Cyan "Running Bleachbit with the following cleaners:" 428 | Show-Output $bleachbit_cleaners 429 | if (Test-Path $bleachbit_path_native) { 430 | & $bleachbit_path_native --clean $bleachbit_cleaners 431 | } else { 432 | & $bleachbit_path_x86 --clean $bleachbit_cleaners 433 | } 434 | } else { 435 | Show-Output -ForegroundColor Red "BleachBit could not be installed." 436 | } 437 | } else { 438 | Show-Output "Skipping BleachBit, as the parameters -Clean or -Deep have not been given." 439 | } 440 | 441 | # If an Intel computer does not have Intel DSA installed 442 | $IntelDSAPath = "${env:ProgramFiles(x86)}\Intel\Driver and Support Assistant\DSATray.exe" 443 | $IntelDSAInstalled = Test-Path "${IntelDSAPath}" 444 | if (Get-IsVirtualMachine) { 445 | Show-Output "Skipping Intel DSA installation on a virtual machine." 446 | } elseif ((-not $IntelDSAInstalled) -and ($ComputerInfo.CsProcessors[0].Manufacturer.ToLower() -contains "intel")) { 447 | Show-Output -ForegroundColor Cyan "Detected an Intel CPU. Installing Intel Driver & Support Assistant." 448 | choco install intel-dsa -y 449 | $IntelDSAInstalled = Test-Path "${IntelDSAPath}" 450 | } 451 | 452 | # Game updates (non-blocking) 453 | # Todo: Create a function for these, which would check for both Program Files (x86) and Program Files, as the former does not exist on 32-bit systems. 454 | # https://stackoverflow.com/a/19015642/ 455 | 456 | if (-not $IsDomainJoined) { 457 | Show-Output -ForegroundColor Cyan "Installing game updates. (If this is a work computer, probably no games will be found.)" 458 | } 459 | 460 | $steam_path="${env:ProgramFiles(x86)}\Steam\Steam.exe" 461 | if (Test-Path $steam_path) { 462 | Show-Output "Starting Steam for updates." 463 | & $steam_path 464 | } elseif (-not $IsDomainJoined) { 465 | Show-Output "Steam was not found." 466 | } 467 | 468 | $battle_net_path="${env:ProgramFiles(x86)}\Battle.net\Battle.net Launcher.exe" 469 | if (Test-Path $battle_net_path) { 470 | Show-Output "Starting Battle.net for updates." 471 | & $battle_net_path 472 | } elseif (-not $IsDomainJoined) { 473 | Show-Output "Battle.net was not found." 474 | } 475 | 476 | $epic_games_path="${env:ProgramFiles(x86)}\Epic Games\Launcher\Portal\Binaries\Win32\EpicGamesLauncher.exe" 477 | if (Test-Path $epic_games_path) { 478 | Show-Output "Staring Epic Games Launcher for updates." 479 | & $epic_games_path 480 | } elseif (-not $IsDomainJoined) { 481 | Show-Output "Epic Games Launcher was not found." 482 | } 483 | 484 | $origin_path="${env:ProgramFiles(x86)}\Origin\Origin.exe" 485 | if (Test-Path $origin_path) { 486 | Show-Output "Starting Origin for updates." 487 | & $origin_path 488 | } elseif (-not $IsDomainJoined) { 489 | Show-Output "Origin was not found." 490 | } 491 | 492 | $ubisoft_connect_path="${env:ProgramFiles(x86)}\Ubisoft\Ubisoft Game Launcher\UbisoftConnect.exe" 493 | if (Test-Path $ubisoft_connect_path) { 494 | Show-Output "Starting Ubisoft Connect for updates." 495 | & $ubisoft_connect_path 496 | } elseif (-not $IsDomainJoined) { 497 | Show-Output "Ubisoft Connect was not found." 498 | } 499 | 500 | $riot_client_path="C:\Riot Games\Riot Client\RiotClientServices.exe" 501 | if (Test-Path $riot_client_path) { 502 | Show-Output "Starting Riot Games client for League of Legends updates." 503 | & $riot_client_path --launch-product=league_of_legends --launch-patchline=live 504 | } elseif (-not $IsDomainJoined) { 505 | Show-Output "Riot Games client was not found." 506 | } 507 | 508 | $minecraft_path="${env:ProgramFiles(x86)}\Minecraft Launcher\MinecraftLauncher.exe" 509 | if (Test-Path $minecraft_path) { 510 | Show-Output "Starting Minecraft for updates." 511 | & $minecraft_path 512 | } elseif (-not $IsDomainJoined) { 513 | Show-Output "Minecraft was not found." 514 | } 515 | 516 | 517 | # Misc non-blocking tasks 518 | 519 | $kingston_ssd_manager_path = "${env:ProgramFiles(x86)}\Kingston_SSD_Manager\KSM.exe" 520 | if ($Reboot -or $Shutdown) { 521 | Show-Output "Kingston SSD Manager will not be started, as automatic reboot or shutdown is enabled." 522 | } elseif (Test-Path $kingston_ssd_manager_path) { 523 | Show-Output -ForegroundColor Cyan "Starting Kingston SSD Manager to check for updates. If there are any, plase wait that the maintenance script is ready before installing them to ensure that no other updates will interfere with them." 524 | & $kingston_ssd_manager_path 525 | } else { 526 | Show-Output "Kingston SSD Manager was not found." 527 | } 528 | 529 | if ($Clean -or $Deep) { 530 | if (Test-CommandExists "cleanmgr") { 531 | Show-Output -ForegroundColor Cyan "Running Windows disk cleanup. This will open some windows about `"low disk space condition`". You can close them when they are ready." 532 | # This command is non-blocking 533 | cleanmgr /verylowdisk 534 | } else { 535 | # Cleanmgr is not installed on Hyper-V Server 536 | Show-Output "Windows disk cleanup was not found." 537 | } 538 | } else { 539 | Show-Output "Skipping Windows disk cleanup, as the parameters -Clean or -Deep has not been specified." 540 | } 541 | 542 | # Windows Store app updates (partially blocking) 543 | # May update Lenovo Vantage, and therefore needs to be before it. 544 | Show-Output -ForegroundColor Cyan "Updating Windows Store apps." 545 | Import-Module Appx 546 | Get-CimInstance -Namespace "Root\cimv2\mdm\dmmap" -ClassName "MDM_EnterpriseModernAppManagement_AppManagement01" | Invoke-CimMethod -MethodName UpdateScanMethod 547 | 548 | 549 | # --- 550 | # Misc blocking tasks 551 | # --- 552 | 553 | # Operations that are delayed, unless running first-time setup 554 | Add-ScriptShortcuts 555 | Set-RepoPermissions 556 | 557 | $NIPackageManagerUpdaterPath = "${env:ProgramFiles}\National Instruments\NI Package Manager\Updater\Install.exe" 558 | if (Test-Path "$NIPackageManagerUpdaterPath") { 559 | Show-Output -ForegroundColor Cyan "Updating NI Package Manager. This will open a window where you have to install the update." 560 | Show-Output -ForegroundColor Cyan "If you get a message saying that the program cannot be upgraded, it means you already have the latest version." 561 | Show-Output -ForegroundColor Cyan "In this case please close the window so that the script may continue." 562 | Show-Output -ForegroundColor Cyan "Once this update is ready, the NI Package Manager itself may open. Please close it. This script will then continue automatically and reopen it with the correct options enabled." 563 | Start-Process -NoNewWindow -Wait "$NIPackageManagerUpdaterPath" 564 | } else { 565 | Show-Output "NI Package Manager updater was not found." 566 | } 567 | $NIPackageManagerPath = "${env:ProgramFiles}\National Instruments\NI Package Manager\NIPackageManager.exe" 568 | if (Test-Path $NIPackageManagerPath) { 569 | Show-Output -ForegroundColor Cyan "Running NI Package Manager to check for updates." 570 | Show-Output -ForegroundColor Cyan "Please install the suggested updates and then close the package manager. This script will then continue automatically." 571 | Start-Process -NoNewWindow -Wait "$NIPackageManagerPath" -ArgumentList "--initial-view=Updates","--output-transactions","--prevent-reboot" 572 | } else { 573 | Show-Output "NI Package Manager was not found." 574 | } 575 | 576 | if (Test-CommandExists "docker") { 577 | Show-Output -ForegroundColor Cyan "Cleaning Docker" 578 | if ($Docker) {docker system prune -f -a} 579 | else {docker system prune -f} 580 | } else { 581 | Show-Output "Docker was not found." 582 | } 583 | 584 | # Driver updates 585 | # This should be the last step in the script so that its updates are not installed during other updates. 586 | if ($Reboot -or $Shutdown) { 587 | Show-Output -ForegroundColor Cyan "Driver updates will not be started, as automatic reboot or shutdown is enabled." 588 | # } elseif ((Test-CommandExists "Test-PendingReboot") -and (Test-PendingReboot -SkipConfigurationManagerClientCheck -SkipPendingFileRenameOperationsCheck).IsRebootPending) { 589 | } elseif (Test-RebootPending) { 590 | Show-Output -ForegroundColor Cyan "Driver updates will not be started, as the computer is pending a reboot." 591 | } else { 592 | # Lenovo Vantage (non-blocking) 593 | if ($LenovoCommercialVantageInstalled) { 594 | Show-Output -ForegroundColor Cyan "Starting Lenovo Commercial Vantage for updates. Please select `"Check for system updates`" and install the suggested updates." 595 | Start-Process shell:appsFolder\E046963F.LenovoSettingsforEnterprise_k1h2ywk1493x8!App 596 | } elseif ($LenovoVantageInstalled) { 597 | Show-Output -ForegroundColor Cyan "Starting Lenovo Vantage for updates. Please select `"Check for system updates`" and install the suggested updates." 598 | Start-Process shell:appsFolder\E046963F.LenovoCompanion_k1h2ywk1493x8!App 599 | } else { 600 | Show-Output "Lenovo Vantage was not found." 601 | } 602 | 603 | # Intel Driver & Support Assistant (non-blocking) 604 | if ($IntelDSAInstalled) { 605 | Start-Process -NoNewWindow "${IntelDSAPath}" 606 | Start-Process "https://www.intel.com/content/www/us/en/support/intel-driver-support-assistant.html" 607 | } else { 608 | Show-Output "Intel Driver & Support Assistant was not found." 609 | } 610 | } 611 | 612 | if (Test-CommandExists "Update-Help") { 613 | Show-Output -ForegroundColor Cyan "Updating PowerShell help. All modules don't have help info, and therefore this may produce errors, which is OK." 614 | Update-Help 615 | } else { 616 | Show-Output "Help updates are not supported by this PowerShell version." 617 | } 618 | 619 | Show-Output -ForegroundColor Cyan "Optimizing drives. SSDs will be trimmed and HDDs defragmented." 620 | Show-Output -ForegroundColor Cyan "If some of the connected drives (e.g. USB flash sticks and SD cards) don't support optimization, this will produce errors, which is OK." 621 | Get-Volume | ForEach-Object { 622 | if ($_.DriveLetter) { 623 | Optimize-Volume -DriveLetter $_.DriveLetter -Verbose 624 | } 625 | } 626 | 627 | # Antivirus 628 | if (Test-CommandExists "Update-MpSignature") { 629 | Show-Output -ForegroundColor Cyan "Updating Windows Defender definitions. If you have another antivirus program installed, Windows Defender may be disabled, causing this to fail." 630 | Update-MpSignature 631 | } else { 632 | Show-Output -ForegroundColor Red "Virus definition updates are not supported. Check them manually." 633 | } 634 | if (Test-CommandExists "Start-MpScan") { 635 | Show-Output -ForegroundColor Cyan "Running Windows Defender full scan. If you have another antivirus program installed, Windows Defender may be disabled, causing this to fail." 636 | Start-MpScan -ScanType "FullScan" 637 | } else { 638 | Show-Output -ForegroundColor Red "Virus scan is not supported. Run it manually." 639 | } 640 | 641 | if ($Zerofree) { 642 | .\zero-free-space.ps1 -DriveLetter "C" 643 | } 644 | 645 | Show-Output -ForegroundColor Cyan "Writing maintenance timestamp." 646 | Get-Date -Format "o" | Out-File $TimestampPath 647 | $ThisRunDate = [Datetime](Get-Content $TimestampPath) 648 | Show-Output -ForegroundColor Cyan "Saved timestamp: ${ThisRunDate}" 649 | 650 | Show-Output -ForegroundColor Green "The maintenance script is ready." 651 | if ($Reboot) { 652 | Show-Output -ForegroundColor Cyan "The computer will be rebooted in 10 seconds." 653 | # The /g switch will automatically login and lock the current user, if this feature is enabled in Windows settings." 654 | shutdown /g /t 10 /c "Mika's maintenance script is ready. Rebooting." 655 | } elseif ($Shutdown) { 656 | Show-Output -ForegroundColor Cyan "The computer will be shut down in 10 seconds." 657 | shutdown /s /t 10 /c "Mika's maintenance script is ready. Shutting down." 658 | } else { 659 | # if ((Test-CommandExists "Test-PendingReboot") -and (Test-PendingReboot -SkipConfigurationManagerClientCheck -SkipPendingFileRenameOperationsCheck).IsRebootPending) { 660 | if (Test-RebootPending) { 661 | Show-Output -ForegroundColor Cyan "The computer is pending a reboot. Please reboot the computer, once all the updater windows that are open say that they are ready." 662 | } 663 | Show-Output -ForegroundColor Green "You can now close this window." 664 | } 665 | 666 | Stop-Transcript 667 | 668 | if ($Scheduled) { 669 | Read-Host -Prompt "Press any key to continue" 670 | Exit 671 | } 672 | -------------------------------------------------------------------------------- /Utils.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Utility methods for scripts 4 | #> 5 | 6 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidAssignmentToAutomaticVariable", "", Justification="Overriding only for old PowerShell versions")] 7 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "LogPath", Justification="Used in scripts")] 8 | param() 9 | 10 | # Compatibility for old PowerShell versions 11 | if($PSVersionTable.PSVersion.Major -lt 3) { 12 | # Configure PSScriptRoot variable for old PowerShell versions 13 | # https://stackoverflow.com/a/8406946/ 14 | # https://stackoverflow.com/a/5466355/ 15 | $invocation = (Get-Variable MyInvocation).Value 16 | $PSScriptRoot = Split-Path $invocation.MyCommand.Path 17 | 18 | # Implement missing features 19 | 20 | function Invoke-WebRequest { 21 | <# 22 | .SYNOPSIS 23 | Download a file from the web 24 | .LINK 25 | https://stackoverflow.com/a/43544903 26 | #> 27 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidOverwritingBuiltInCmdlets", "", Justification="Overriding only for old PowerShell versions")] 28 | param( 29 | [Parameter(Mandatory=$true)][string]$Uri, 30 | [Parameter(Mandatory=$true)][string]$OutFile 31 | ) 32 | $WebClient_Obj = New-Object System.Net.WebClient 33 | # TODO: this fails for some reason 34 | $WebClient_Obj.DownloadFile($Uri, $OutFile) 35 | } 36 | 37 | function Expand-Archive { 38 | <# 39 | .SYNOPSIS 40 | Zip file decompression 41 | .DESCRIPTION 42 | TODO: test that this works 43 | .LINK 44 | https://stackoverflow.com/a/54687028 45 | #> 46 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidOverwritingBuiltInCmdlets", "", Justification="Overriding only for old PowerShell versions")] 47 | param( 48 | [Parameter(Mandatory=$true)][string]$Path, 49 | [Parameter(Mandatory=$true)][string]$DestinationPath 50 | ) 51 | $shell_ComObject = New-Object -ComObject shell.application 52 | $zip_file = $shell_ComObject.namespace($Path) 53 | $folder = $shell_ComObject.namespace($DestinationPath) 54 | $folder.Copyhere($zip_file.items()) 55 | } 56 | } 57 | 58 | # This has to be after the compatibility section so that $PSScriptRoot is defined 59 | # $RepoPath = Split-Path $PSScriptRoot -Parent 60 | $RepoPath = $PSScriptRoot 61 | 62 | # Create sub-directories of the repo so that the scripts don't have to create them. 63 | New-Item -Path "$RepoPath" -Name "Downloads" -ItemType "directory" -Force | Out-Null 64 | New-Item -Path "$RepoPath" -Name "Logs" -ItemType "directory" -Force | Out-Null 65 | $Downloads = "${RepoPath}\Downloads" 66 | $LogPath = "${RepoPath}\Logs" 67 | 68 | # Define some useful paths 69 | $CommonDesktopPath = [Environment]::GetFolderPath("CommonDesktopDirectory") 70 | $DesktopPath = [Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop) 71 | $StartPath = Get-Location 72 | 73 | # Define some useful variables 74 | $UserDir = ([System.IO.DirectoryInfo]"${env:UserProfile}").FullName 75 | $RepoInUserDir = ([System.IO.DirectoryInfo]"${RepoPath}").FullName.StartsWith($UserDir) 76 | 77 | # This will be filled by Get-InstalledSoftware 78 | $InstalledSoftware = $null 79 | 80 | # Force Invoke-WebRequest to use TLS 1.2 and TLS 1.3 instead of the insecure TLS 1.0, which is the default. 81 | # https://stackoverflow.com/a/41618979 82 | if ([Net.SecurityProtocolType]::Tls13) { 83 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 84 | } else { 85 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 86 | } 87 | 88 | 89 | function Add-ScriptShortcuts { 90 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Plural on purpose")] 91 | param() 92 | 93 | if ($RepoInUserDir) { 94 | $BasePath = $DesktopPath 95 | } else { 96 | if (! (Test-Admin)) { 97 | Show-Output "Cannot create script shortcuts for all users, as the script is not elevated (yet)." 98 | return 99 | } 100 | $BasePath = $CommonDesktopPath 101 | } 102 | $InstallShortcutPath = "${BasePath}\Installer.lnk" 103 | $MaintenanceShortcutPath = "${BasePath}\Maintenance.lnk" 104 | $WindowsUpdateShortcutPath = "${BasePath}\Windows Update.lnk" 105 | $ReportShortcutPath = "${BasePath}\Create IT report.lnk" 106 | $RepoShortcutPath = "${BasePath}\windows-scripts.lnk" 107 | 108 | # if(!(Test-Path $InstallShortcutPath)) { 109 | New-Shortcut -Path $InstallShortcutPath -TargetPath "powershell" -Arguments "-File ${RepoPath}\Install-Software.ps1" -WorkingDirectory "${RepoPath}" -IconLocation "shell32.dll,21" | Out-Null 110 | # } 111 | # if(!(Test-Path $MaintenanceShortcutPath)) { 112 | New-Shortcut -Path $MaintenanceShortcutPath -TargetPath "powershell" -Arguments "-File ${RepoPath}\Maintenance.ps1" -WorkingDirectory "${RepoPath}" -IconLocation "shell32.dll,80" | Out-Null 113 | # } 114 | New-Shortcut -Path $ReportShortcutPath -TargetPath "powershell" -Arguments "-File ${RepoPath}\Report.ps1" -WorkingDirectory "${RepoPath}" -IconLocation "shell32.dll,1" | Out-Null 115 | New-Shortcut -Path $RepoShortcutPath -TargetPath "${RepoPath}" | Out-Null 116 | 117 | $WindowsVersion = [System.Environment]::OSVersion.Version.Major 118 | if ($WindowsVersion -ge 8) { 119 | if ((Get-CimInstance Win32_OperatingSystem).Caption -Match "Windows 10") { 120 | $WindowsUpdateTargetPath = "ms-settings:windowsupdate-action" 121 | } else { 122 | $WindowsUpdateTargetPath = "ms-settings:windowsupdate" 123 | } 124 | New-Shortcut -Path $WindowsUpdateShortcutPath -TargetPath "${env:windir}\explorer.exe" -Arguments "${WindowsUpdateTargetPath}" -IconLocation "shell32.dll,46" | Out-Null 125 | } 126 | } 127 | 128 | function Clear-Path { 129 | <# 130 | .SYNOPSIS 131 | Clear the path for e.g. unpacking a program. 132 | .DESCRIPTION 133 | Return values: 0 not cleared, 1 cleared, 2 no need 134 | #> 135 | # [Parameter(Mandatory=$true)] 136 | [OutputType([bool])] 137 | param( 138 | [Parameter(Mandatory=$true)][string]$Path 139 | ) 140 | if (Test-Path "${Path}") { 141 | $Decision = $Host.UI.PromptForChoice("The path `"${Path}`" already exists.", "Shall I clear and overwrite it?", ("y", "n"), 0) 142 | if ($Decision -eq 0) { 143 | Remove-Item -Path "${Path}" -Recurse 144 | return 1 145 | } 146 | return 0 147 | } 148 | return 2 149 | } 150 | 151 | function Elevate { 152 | <# 153 | .SYNOPSIS 154 | Elevate the current process to admin privileges. 155 | .LINK 156 | https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-7.2#example-5--start-powershell-as-an-administrator 157 | #> 158 | param( 159 | [Parameter(Mandatory=$true)][string]$command 160 | ) 161 | if (! (Test-Admin)) { 162 | if ($Elevated) 163 | { 164 | Show-Output "Elevation did not work." 165 | } 166 | else { 167 | Show-Output "This script requires admin access. Elevating." 168 | Show-Output "${command}" 169 | $ArgumentList = ('-NoProfile -NoExit -Command "cd {0}; {1} -Elevated"' -f ($pwd, $command)) 170 | # Use newer PowerShell if available. 171 | if (Test-CommandExists "pwsh") {$shell = "pwsh"} else {$shell = "powershell"} 172 | # Use Windows Terminal if available 173 | # if (Test-CommandExists "wt") { 174 | # # https://stackoverflow.com/a/63163528 175 | # # This has problems in parsing the semicolon 176 | # Start-Process -FilePath "wt" -Verb RunAs -ArgumentList ('new-tab {0} {1}' -f ($shell, $ArgumentList)) 177 | # } else { 178 | Start-Process -FilePath "$shell" -Verb RunAs -ArgumentList "${ArgumentList}" 179 | # } 180 | Show-Output "The script has been started in another window. You can close this window now." -ForegroundColor Green 181 | } 182 | Exit 183 | } 184 | } 185 | 186 | function Find-First { 187 | <# 188 | .SYNOPSIS 189 | Find the first file that matches the given filter 190 | .LINK 191 | https://stackoverflow.com/a/1500148/ 192 | #> 193 | [OutputType([System.IO.FileInfo])] 194 | param( 195 | [string]$Filter, 196 | [string]$Path 197 | ) 198 | return @(Get-ChildItem -Filter "${Filter}" -Path "${Path}")[0] 199 | } 200 | 201 | function Get-CertificateInfo { 202 | <# 203 | .SYNOPSIS 204 | Get basic info of a certificate as a string 205 | #> 206 | [OutputType([string])] 207 | param([Parameter(Mandatory=$true)][System.Security.Cryptography.X509Certificates.X509Certificate]$Certificate) 208 | $NotBefore = $Certificate.NotBefore.ToUniversalTime().ToString("yyyy-MM-dd") 209 | $NotAfter = $Certificate.NotAfter.ToUniversalTime().ToString("yyyy-MM-dd") 210 | $Algorithm = $Certificate.SignatureAlgorithm.FriendlyName 211 | return "$($Certificate.Subject) (${NotBefore} - ${NotAfter}, ${Algorithm}, " ` 212 | + "Currently valid: $($Certificate.Verify()), Thumbprint: $($Certificate.Thumbprint))" 213 | } 214 | 215 | function Get-InstallBitness { 216 | [OutputType([string])] 217 | param( 218 | [string]$x86 = "x86", 219 | [string]$x86_64 = "x86_64", 220 | [switch]$Quiet = $false 221 | ) 222 | if ([System.Environment]::Is64BitOperatingSystem) { 223 | if (-not $Quiet) { 224 | Show-Information "64-bit operating system detected. Installing 64-bit version." 225 | } 226 | return $x86_64 227 | } 228 | if (-not $Quiet) { 229 | Show-Information "32-bit operating system detected. Installing 32-bit version." 230 | } 231 | return $x86 232 | } 233 | 234 | function Get-InstalledSoftware { 235 | <# 236 | .SYNOPSIS 237 | Get a list of installed software. 238 | .NOTES 239 | This uses Win32_Product, which is abysmally slow. Find a better method if possible. 240 | Unfortunately, Get-Product does not work in PowerShell 7. 241 | .LINK 242 | https://gregramsey.net/2012/02/20/win32_product-is-evil/ 243 | #> 244 | [OutputType([System.Array])] 245 | param() 246 | if ($null -eq $script:InstalledSoftware) { 247 | Show-Information "Getting the list of installed software. This may take a while." 248 | $script:InstalledSoftware = Get-CimInstance -ClassName Win32_Product 249 | } 250 | return $script:InstalledSoftware 251 | } 252 | 253 | function Get-IsDomainJoined { 254 | [OutputType([bool])] 255 | param() 256 | return (Get-CimInstance -ClassName Win32_ComputerSystem).PartOfDomain 257 | } 258 | 259 | function Get-IsHeadlessServer { 260 | <# 261 | .SYNOPSIS 262 | Test whether the computer is a headless server (e.g. Windows Server Core or Hyper-V Server) 263 | .LINK 264 | https://serverfault.com/a/529131 265 | #> 266 | [OutputType([bool])] 267 | param() 268 | # return Get-IsServer -and (-not (Get-WindowsFeature -Name 'Server-Gui-Shell').InstallState -eq 2) 269 | return Test-Path "${env:windir}\explorer.exe" 270 | } 271 | 272 | function Get-IsServer { 273 | [OutputType([bool])] 274 | param() 275 | return (Get-CimInstance -Class Win32_OperatingSystem).ProductType -ne 1 276 | } 277 | 278 | function Get-IsVirtualMachine { 279 | [OutputType([bool])] 280 | param() 281 | return Get-IsVirtualBoxMachine -or ((Get-CimInstance Win32_ComputerSystem).Model -contains "Virtual") 282 | } 283 | 284 | function Get-IsVirtualBoxMachine { 285 | [OutputType([bool])] 286 | param() 287 | return ( 288 | (Test-Path "${env:ProgramFiles}\Oracle\VirtualBox Guest Additions") -or 289 | ((Get-CimInstance Win32_ComputerSystem).Model -eq "VirtualBox") 290 | ) 291 | } 292 | 293 | function Get-YesNo { 294 | [OutputType([bool])] 295 | param( 296 | [Parameter(Mandatory=$true)][string]$Question 297 | ) 298 | while ($true) { 299 | $Confirmation = Read-Host "${Question} [y/n]" 300 | if ($Confirmation -eq "y") { 301 | return $True 302 | } 303 | if ($Confirmation -eq "n") { 304 | return $False 305 | } 306 | } 307 | } 308 | 309 | function Install-Chocolatey { 310 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingInvokeExpression", "", Justification="Chocolatey installation requires Invoke-Expression")] 311 | param( 312 | [switch]$Force 313 | ) 314 | if($Force -Or (-Not (Test-CommandExists "choco"))) { 315 | Show-Output "Installing the Chocolatey package manager." 316 | Set-ExecutionPolicy Bypass -Scope Process -Force 317 | # This is already configured globally above 318 | # [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 319 | Invoke-Expression ((New-Object System.Net.WebClient).DownloadString("https://chocolatey.org/install.ps1")) 320 | } 321 | } 322 | 323 | function Install-Executable { 324 | [OutputType([int])] 325 | param( 326 | [string]$Name, 327 | [string]$Path, 328 | [switch]$BypassAuthenticode = $false 329 | ) 330 | # Validate the file path 331 | try { 332 | $File = Get-Item "${Path}" -ErrorAction Stop 333 | } catch { 334 | if ($Path.StartsWith("${RepoPath}")) { 335 | Show-Output -ForegroundColor Red "${Name} installer was not found at ${Path}. Do you have the network drive mounted?" 336 | Show-Output -ForegroundColor Red "It could be that your computer does not have the necessary group policies applied. Applying. You will need to reboot for the changes to become effective." 337 | gpupdate /force 338 | } else { 339 | Show-Output -ForegroundColor Red "${Name} installer was not found at ${Path}." 340 | } 341 | return 1 342 | } 343 | # Validate Authenticode 344 | if (-not $BypassAuthenticode) { 345 | try { 346 | Test-AuthenticodeSignature -FilePath "${Path}" 347 | } catch { 348 | return 2 349 | } 350 | } 351 | # Install the executable 352 | Show-Output "Installing ${Name}" 353 | if ($File.Extension -eq ".msi") { 354 | $Process = Start-Process -NoNewWindow -Wait -PassThru "msiexec" -ArgumentList "/i","${Path}" 355 | } else { 356 | $Process = Start-Process -NoNewWindow -Wait -PassThru "${Path}" 357 | } 358 | $ExitCode = $Process.ExitCode 359 | if ($ExitCode) { 360 | Show-Output -ForegroundColor Red "${Name} installation returned non-zero exit code ${ExitCode}. Perhaps the installation failed?" 361 | } 362 | return $ExitCode 363 | } 364 | 365 | function Install-FromUri { 366 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingBrokenHashAlgorithms", "", Justification="Legacy MD5 support is on purpose for backwards compatibility")] 367 | [OutputType([int])] 368 | param( 369 | [Parameter(mandatory=$true)][string]$Name, 370 | [Parameter(mandatory=$true)][string]$Uri, 371 | [Parameter(mandatory=$true)][string]$Filename, 372 | [Parameter(mandatory=$false)][string]$UnzipFolderName, 373 | [Parameter(mandatory=$false)][string]$UnzippedFilePath, 374 | [Parameter(mandatory=$false)][string]$MD5, 375 | [Parameter(mandatory=$false)][string]$SHA256, 376 | [switch]$BypassAuthenticode = $false 377 | ) 378 | Show-Output "Downloading ${Name}" 379 | $Path = "${Downloads}\${Filename}" 380 | 381 | # Test if the file already exists and matches the given hash 382 | $FileAlreadyOK = $false 383 | if (Test-Path $Path) { 384 | if ($PSBoundParameters.ContainsKey("SHA256")) { 385 | $FileHash = (Get-FileHash -Path "${Path}" -Algorithm "SHA256").Hash 386 | if ($FileHash -eq $SHA256) { 387 | $FileAlreadyOK = $true 388 | Show-Output "The file `"${Path}`" is already downloaded and has the correct SHA-256 hash ${SHA256}. Skipping download." 389 | } else { 390 | Show-Output "The file `"${Path}` was already downloaded, but had an incorrect SHA-256 hash. " ` 391 | + "(Expected ${SHA256}, got ${FileHash}.) The previous download may have been interrupted. Downloading again." 392 | } 393 | } elseif ($PSBoundParameters.ContainsKey("MD5")) { 394 | $FileHash = (Get-FileHash -Path "${Path}" -Algorithm "MD5").Hash 395 | if ($FileHash -eq $MD5) { 396 | $FileAlreadyOK = $true 397 | Show-Output "The file `"${Path}`" is already downloaded and has the correct MD5 hash ${MD5}. Skipping download." 398 | } else { 399 | Show-Output "The file `"${Path}` was already downloaded, but had an incorrect MD5 hash. " ` 400 | + "(Expected ${MD5}, got ${FileHash}.) The previous download may have been interrupted. Downloading again." 401 | } 402 | } 403 | } 404 | 405 | # Download the file 406 | if (-not $FileAlreadyOK) { 407 | Invoke-WebRequestFast -Uri "${Uri}" -OutFile "${Path}" 408 | } 409 | 410 | # Get the file object 411 | try { 412 | $File = Get-Item "${Path}" -ErrorAction Stop 413 | } catch { 414 | Show-Output "Downloaded file was not found at ${Path}" 415 | return 1 416 | } 417 | 418 | # Verify the file checksum if not already verified 419 | if (-not $FileAlreadyOK) { 420 | if ($PSBoundParameters.ContainsKey("SHA256")) { 421 | $FileHash = (Get-FileHash -Path "${Path}" -Algorithm "SHA256").Hash 422 | if ($FileHash -eq $SHA256) { 423 | Show-Output "SHA256 checksum OK" 424 | } else { 425 | Show-Output -ForegroundColor Red "Downloaded file has an invalid SHA256 checksum. Expected: ${SHA256}, got: ${FileHash}" 426 | return 1 427 | } 428 | } elseif ($PSBoundParameters.ContainsKey("MD5")) { 429 | $FileHash = (Get-FileHash -Path "${Path}" -Algorithm "MD5").Hash 430 | if ($FileHash -eq $MD5) { 431 | Show-Output "MD5 checksum OK" 432 | } else { 433 | Show-Output -ForegroundColor Red "Downloaded file has an invalid MD5 checksum. Expected: ${MD5}, got: ${FileHash}" 434 | return 1 435 | } 436 | } 437 | } 438 | 439 | # Process the downloaded file 440 | if ($File.Extension -eq ".zip") { 441 | if (-not $PSBoundParameters.ContainsKey("UnzipFolderName")) { 442 | Show-Output -ForegroundColor Red "UnzipFolderName was not provided for a zip file." 443 | return 1 444 | } 445 | if (-not $PSBoundParameters.ContainsKey("UnzippedFilePath")) { 446 | Show-Output -ForegroundColor Red "UnzippedFilePath was not provided for a zip file." 447 | return 1 448 | } 449 | Show-Output "Extracting ${Name}" 450 | Expand-Archive -Path "${Path}" -DestinationPath "${Downloads}\${UnzipFolderName}" -Force 451 | $ExecutablePath = "${Downloads}\${UnzipFolderName}\${UnzippedFilePath}" 452 | } else { 453 | $ExecutablePath = "${Downloads}\${Filename}" 454 | } 455 | Install-Executable -Name "${Name}" -Path "${ExecutablePath}" -BypassAuthenticode:$BypassAuthenticode 456 | } 457 | 458 | function Install-Geekbench { 459 | <# 460 | .SYNOPSIS 461 | Install the Geekbench performance test benchmarks 462 | .LINK 463 | https://www.geekbench.com/download/windows/ 464 | .LINK 465 | https://www.geekbench.com/legacy/ 466 | #> 467 | param( 468 | [string[]]$GeekbenchVersions = @("6.2.1", "5.5.1", "4.4.4", "3.4.4", "2.4.3") 469 | ) 470 | foreach ($Version in $GeekbenchVersions) { 471 | Show-Output "Downloading Geekbench ${Version}" 472 | $Filename = "Geekbench-$Version-WindowsSetup.exe" 473 | $Url = "https://cdn.geekbench.com/$Filename" 474 | Invoke-WebRequest -Uri "$Url" -OutFile "${Downloads}\${Filename}" 475 | } 476 | foreach ($Version in $GeekbenchVersions) { 477 | Show-Output "Installing Geekbench ${Version}" 478 | Start-Process -NoNewWindow -Wait "${Downloads}\Geekbench-${Version}-WindowsSetup.exe" 479 | } 480 | } 481 | 482 | function Install-PTS { 483 | param( 484 | [string]$PTS_version = "10.8.4", 485 | [bool]$Force = $False 486 | ) 487 | $PTS = "${Env:SystemDrive}\phoronix-test-suite\phoronix-test-suite.bat" 488 | if ((-not (Test-Path "$PTS")) -or $Force) { 489 | Show-Output "Downloading Phoronix Test Suite (PTS)." 490 | Invoke-WebRequest -Uri "https://github.com/phoronix-test-suite/phoronix-test-suite/archive/v${PTS_version}.zip" -OutFile "${Downloads}\phoronix-test-suite-${PTS_version}.zip" 491 | Show-Output "Extracting Phoronix Test Suite (PTS)" 492 | Expand-Archive -LiteralPath "${Downloads}\phoronix-test-suite-${PTS_version}.zip" -DestinationPath "${Downloads}\phoronix-test-suite-${PTS_version}" -Force 493 | # The installation script needs to be executed in its directory 494 | Set-Location "${Downloads}\phoronix-test-suite-${PTS_version}\phoronix-test-suite-${PTS_version}" 495 | Show-Output "Installing Phoronix Test Suite (PTS)." 496 | Show-Output "You may get prompts asking whether you accept the EULA and whether to send anonymous usage statistics." 497 | Show-Output "Please select yes to the former and preferably to the latter as well." 498 | & ".\install.bat" 499 | Set-Location "$StartPath" 500 | if (-Not (Test-Path "$PTS")) { 501 | Show-Output -ForegroundColor Red "Phoronix Test Suite (PTS) installation failed." 502 | exit 1 503 | } 504 | Show-Output "Phoronix Test Suite (PTS) has been installed. It is highly recommended that you log in now so that you can manage the uploaded results." 505 | & "$PTS" openbenchmarking-login 506 | Show-Output "You must now configure the batch mode according to your preferences. Some of the settings may get overwritten later by this script." 507 | & "$PTS" batch-setup 508 | } 509 | & "$PTS" openbenchmarking-refresh 510 | } 511 | 512 | function Install-Winget { 513 | [OutputType([bool])] 514 | param() 515 | if (Test-CommandExists "winget") { 516 | return $true 517 | } 518 | if (Get-AppxPackage -Name "Microsoft.DesktopAppInstaller") { 519 | Show-Output "App Installer seems to be installed on your system, but Winget was not found." 520 | return $false 521 | } 522 | if (Get-AppxPackage -Name "Microsoft.WindowsStore") { 523 | Show-Output "App Installer appears not to be installed. Please close this window and install it from the Windows Store. Then restart this script." 524 | Start-Process -Path "https://www.microsoft.com/en-us/p/app-installer/9nblggh4nns1" 525 | $confirmation = Show-Output "If you know what you're doing, you may also continue by writing `"force`", but some features may be disabled.". 526 | while ($confirmation -ne "force") { 527 | $confirmation = Show-Output "Close this window or write `"force`" to continue." 528 | } 529 | return $false; 530 | } 531 | Show-Output "Cannot install App Installer, as Microsoft Store appears not to be installed. This is normal on servers. Winget will not be available." 532 | return $false 533 | } 534 | 535 | function Invoke-WebRequestFast { 536 | <# 537 | .SYNOPSIS 538 | Invoke-WebRequest but without the progress bar that slows it down 539 | .LINK 540 | https://github.com/PowerShell/PowerShell/issues/13414 541 | .LINK 542 | https://learn.microsoft.com/en-us/virtualization/community/team-blog/2017/20171219-tar-and-curl-come-to-windows 543 | #> 544 | param( 545 | [Parameter(Mandatory=$true)][string]$Uri, 546 | [Parameter(Mandatory=$true)][string]$OutFile 547 | ) 548 | if (Test-CommandExists "curl.exe") { 549 | # The --http2 and --http3 arguments are not supported on Windows 11 22H2. 550 | # https://github.com/microsoft/WSL/issues/3141 551 | # --location is used instead of --url so that redirects are followed, which is required for GitHub downloads. 552 | curl.exe --location "${Uri}" --output "${OutFile}" --tlsv1.2 553 | } else { 554 | $PreviousProgressPreference = $ProgressPreference 555 | $ProgressPreference = "SilentlyContinue" 556 | Invoke-WebRequest -Uri $Uri -OutFile $OutFile 557 | $ProgressPreference = $PreviousProgressPreference 558 | } 559 | } 560 | 561 | function New-Junction { 562 | <# 563 | .SYNOPSIS 564 | Create a new directory junction (equivalent to a symlink on Linux) 565 | .LINK 566 | https://github.com/PowerShell/PowerShell/issues/621 567 | #> 568 | [CmdletBinding(SupportsShouldProcess)] 569 | param( 570 | [Parameter(Mandatory=$true)][string]$Path, 571 | [Parameter(Mandatory=$true)][string]$Target, 572 | [Parameter(Mandatory=$false)][boolean]$Backup = $true, 573 | [Parameter(Mandatory=$false)][string]$BackupSuffix = "-old", 574 | [Parameter(Mandatory=$false)][string]$BackupPath 575 | ) 576 | if (Test-Path -Path "${Path}") { 577 | $LinkType = (Get-Item -Path "${Path}" -Force).LinkType 578 | if ($LinkType -or (-not $Backup)) { 579 | Show-Output "Removing old junction at `"${Path}`", LinkType=${LinkType}" 580 | # The -Recurse argument has to be specified to remove a junction. 581 | # This doesn't remove the actual directory or its contents. Please see the link above. 582 | if($PSCmdlet.ShouldProcess($Path, "Remove-Item")) { 583 | Remove-Item "${Path}" -Recurse 584 | } 585 | } else { 586 | if (-not $PSBoundParameters.ContainsKey("BackupPath")) { 587 | $BackupPath = "${Path}${BackupSuffix}" 588 | } 589 | Show-Output "Backing up old directory at `"${Path}`" to `"${BackupPath}`"" 590 | if($PSCmdlet.ShouldProcess($Path, "Move-Item")) { 591 | Move-Item -Path "${Path}" -Destination "${BackupPath}" 592 | } 593 | } 594 | } 595 | Show-Output "Creating junction from `"${Path}`" to `"${Target}`"" 596 | if($PSCmdlet.ShouldProcess($Path, "New-Item")) { 597 | return New-Item -ItemType "Junction" -Path "${Path}" -Target "${Target}" 598 | } 599 | } 600 | 601 | function New-Shortcut { 602 | <# 603 | .SYNOPSIS 604 | Create a .lnk shortcut 605 | .LINK 606 | https://stackoverflow.com/a/9701907 607 | #> 608 | [CmdletBinding(SupportsShouldProcess)] 609 | param( 610 | [Parameter(Mandatory=$true)][string]$Path, 611 | [Parameter(Mandatory=$true)][string]$TargetPath, 612 | [string]$Arguments, 613 | [string]$IconLocation, 614 | [string]$WorkingDirectory 615 | ) 616 | # This does not work, as it throws an error for bare program names, e.g. "powershell" 617 | # if (!(Test-Path "${TargetPath}")) { 618 | # throw [System.IO.FileNotFoundException] "Could not find shortcut target ${TargetPath}" 619 | # } 620 | 621 | $WshShell = New-Object -ComObject WScript.Shell 622 | $Shortcut = $WshShell.CreateShortcut($Path) 623 | $Shortcut.TargetPath = $TargetPath 624 | if (! [string]::IsNullOrEmpty($WorkingDirectory)) { 625 | $Shortcut.WorkingDirectory = $WorkingDirectory 626 | } 627 | if (! [string]::IsNullOrEmpty($Arguments)) { 628 | $Shortcut.Arguments = $Arguments 629 | } 630 | if (! [string]::IsNullOrEmpty($IconLocation)) { 631 | $Shortcut.IconLocation = $IconLocation 632 | } 633 | if($PSCmdlet.ShouldProcess($Path, "Save")) { 634 | $Shortcut.Save() 635 | } 636 | return $Shortcut 637 | } 638 | 639 | function Request-DomainConnection { 640 | [OutputType([bool])] 641 | $IsJoined = Get-IsDomainJoined 642 | if ($IsJoined) { 643 | Show-Output "Your computer seems to be a domain member. Please connect it to the domain network now. A VPN is OK but a physical connection is better." 644 | } 645 | return $IsJoined 646 | } 647 | 648 | function Set-RepoPermissions { 649 | [CmdletBinding(SupportsShouldProcess)] 650 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Plural on purpose")] 651 | param() 652 | 653 | Import-Module Microsoft.PowerShell.Security 654 | if ($RepoInUserDir) { 655 | Show-Output "The repo is installed within the user folder. Ensuring that it's owned by the user." 656 | $ACL = Get-Acl -Path "${RepoPath}" 657 | # This does not include the domain part 658 | # $User = New-Object System.Security.Principal.Ntaccount([Environment]::UserName) 659 | $User = whoami 660 | $CurrentOwner = $ACL.Owner 661 | if ($CurrentOwner -ne $User) { 662 | Show-Output "The repo is owned by `"${CurrentOwner}`". Changing it to: `"${User}`"" 663 | $ACL.SetOwner($User) 664 | if($PSCmdlet.ShouldProcess($RepoPath, "Set-Acl")) { 665 | $ACL | Set-Acl -Path "${RepoPath}" 666 | } 667 | } 668 | } else { 669 | Show-Output "The repo is installed globally. Ensuring that it's protected." 670 | $ACL = Get-Acl -Path "${env:ProgramFiles}" 671 | if($PSCmdlet.ShouldProcess($RepoPath, "Set-Acl")) { 672 | $ACL | Set-Acl -Path "${RepoPath}" 673 | } 674 | $RepoPathObj = (Get-Item $RepoPath) 675 | if (($RepoPathObj.Parent.FullName -eq "Git") -and ($RepoPathObj.Parent.Parent.FullName -eq $env:SystemDrive)) { 676 | Show-Output "The repo is located in the Git folder at the root of the system drive. Protecting the Git folder." 677 | $ACL = Get-Acl -Path $env:SystemDrive 678 | if($PSCmdlet.ShouldProcess($RepoPathObj, "Set-Acl")) { 679 | $ACL | Set-Acl -Path $RepoPathObj.Parent 680 | } 681 | } 682 | } 683 | } 684 | 685 | function Show-Information { 686 | <# 687 | .SYNOPSIS 688 | Write-Information with colors. Use this instead of Write-Host. 689 | #> 690 | param( 691 | [Parameter(Position=0, Mandatory=$true)]$MessageData, 692 | [System.ConsoleColor]$ForegroundColor, 693 | [System.ConsoleColor]$BackgroundColor 694 | # This is set globally 695 | # https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_commonparameters?view=powershell-7.2#-informationaction 696 | # [string]$InformationAction = "Continue" 697 | ) 698 | if ($InformationPreference -eq "SilentlyContinue") { 699 | $CustomInformationPreference = "Continue" 700 | } else { 701 | $CustomInformationPreference = $InformationPreference 702 | } 703 | Show-Stream @PSBoundParameters -Stream "Write-Information" -InformationAction $CustomInformationPreference 704 | } 705 | 706 | function Show-Output { 707 | <# 708 | .SYNOPSIS 709 | Write-Output with colors. 710 | #> 711 | param( 712 | [Parameter(Position=0, Mandatory=$true)]$InputObject, 713 | [System.ConsoleColor]$ForegroundColor, 714 | [System.ConsoleColor]$BackgroundColor 715 | ) 716 | Show-Stream @PSBoundParameters -Stream "Write-Output" 717 | } 718 | 719 | function Show-Stream { 720 | <# 721 | .SYNOPSIS 722 | Show text or an object on the console. 723 | .DESCRIPTION 724 | With color support but without using Write-Host. 725 | .LINK 726 | https://stackoverflow.com/a/4647985/ 727 | #> 728 | # Argument passing 729 | # https://stackoverflow.com/a/62861781/ 730 | param( 731 | [Parameter(Position=0, Mandatory=$true)][Alias("MessageData")]$InputObject, 732 | [System.ConsoleColor]$ForegroundColor, 733 | [System.ConsoleColor]$BackgroundColor, 734 | [string]$Stream = "Write-Output" 735 | ) 736 | if ($PSBoundParameters.ContainsKey("ForegroundColor")) { 737 | $OldForegroundColor = [Console]::ForegroundColor 738 | [Console]::ForegroundColor = $ForegroundColor 739 | } 740 | if ($PSBoundParameters.ContainsKey("BackgroundColor")) { 741 | $OldBackgroundColor = [Console]::BackgroundColor 742 | [Console]::BackgroundColor = $BackgroundColor 743 | } 744 | if ($args) { 745 | & $Stream $InputObject $args 746 | } elseif ($Stream -eq "Write-Information") { 747 | # Write-Information does not support piping 748 | & $Stream $InputObject 749 | } else { 750 | $InputObject | & "${Stream}" 751 | } 752 | if ($PSBoundParameters.ContainsKey("ForegroundColor")) { 753 | [Console]::ForegroundColor = $OldForegroundColor 754 | } 755 | if ($PSBoundParameters.ContainsKey("BackgroundColor")) { 756 | [Console]::BackgroundColor = $OldBackgroundColor 757 | } 758 | } 759 | 760 | function Test-Admin { 761 | <# 762 | .SYNOPSIS 763 | Test whether the script is being run as an administrator 764 | .LINK 765 | https://superuser.com/questions/108207/how-to-run-a-powershell-script-as-administrator 766 | #> 767 | [OutputType([bool])] 768 | $CurrentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent()) 769 | return $CurrentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) 770 | } 771 | 772 | function Test-AuthenticodeSignature { 773 | <# 774 | .SYNOPSIS 775 | Validate that a file has a valid Authenticode signature 776 | #> 777 | param( 778 | [Parameter(Mandatory=$true)][string]$FilePath, 779 | [switch]$Silent = $false 780 | ) 781 | $Signature = Get-AuthenticodeSignature -FilePath "${FilePath}" 782 | $Failed = $Signature.Status -ne "Valid" 783 | if (-not $Silent) { 784 | $Status = "Status: $($Signature.Status) ($($Signature.StatusMessage))" 785 | if ($Failed) { 786 | Show-Output -ForegroundColor Red "Authenticode signature verification failed for `"$($Signature.Path)`"." 787 | Show-Output -ForegroundColor Red "${Status}" 788 | Show-Output -ForegroundColor Red "This may be an indication of malicious tampering with the file." 789 | Show-Output -ForegroundColor Red "Please contact your system administrator or the developers of the software." 790 | } else { 791 | Show-Output "Authenticode signature verification successful for `"$($Signature.Path)`"." 792 | Show-Output "${Status}, Signature type: $($Signature.SignatureType)" 793 | } 794 | $Signer = $Signature.SignerCertificate 795 | if ($Signer) { 796 | Show-Output "Certificate: $(Get-CertificateInfo $Signer)" 797 | } 798 | $TimeStamper = $Signature.TimeStamperCertificate 799 | if ($TimeStamper) { 800 | Show-Output "Timestamper: $(Get-CertificateInfo $TimeStamper)" 801 | } 802 | } 803 | if ($Failed) { 804 | throw "Authenticode signature verification failed for `"$($Signature.Path)`"." 805 | } 806 | } 807 | 808 | function Test-CommandExists { 809 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Not plural")] 810 | [OutputType([bool])] 811 | Param( 812 | [Parameter(Mandatory=$true)][string]$command 813 | ) 814 | $oldPreference = $ErrorActionPreference 815 | $ErrorActionPreference = "stop" 816 | try { 817 | if (Get-Command $command){RETURN $true} 818 | } 819 | catch {RETURN $false} 820 | finally {$ErrorActionPreference=$oldPreference} 821 | } 822 | 823 | function Test-PendingRebootAndExit { 824 | # The PendingReboot module gives false positives about the need to reboot. 825 | # if (Test-CommandExists "Install-Module") { 826 | # Show-Output -ForegroundColor Cyan "Ensuring that the PowerShell reboot checker module is installed. You may now be asked whether to install the NuGet package provider. Please select yes." 827 | # Install-Module -Name PendingReboot -Force 828 | # } 829 | # if ((Test-CommandExists "Test-PendingReboot") -and (Test-PendingReboot -SkipConfigurationManagerClientCheck -SkipPendingFileRenameOperationsCheck).IsRebootPending) { 830 | if (Test-RebootPending) { 831 | Show-Output -ForegroundColor Cyan "A reboot is already pending. Please close this window, reboot the computer and then run this script again." 832 | if (! (Get-YesNo "If you are sure you want to continue regardless, please write `"y`" and press enter.")) { 833 | Exit 0 834 | } 835 | } 836 | } 837 | 838 | function Test-RebootPending { 839 | <# 840 | .SYNOPSIS 841 | Test whether the computer has a reboot pending. The name is chosen not to conflict with the PendingReboot PowerShell module. 842 | .LINK 843 | https://4sysops.com/archives/use-powershell-to-test-if-a-windows-server-is-pending-a-reboot/ 844 | #> 845 | [OutputType([bool])] 846 | $pendingRebootTests = @( 847 | @{ 848 | Name = "RebootPending" 849 | Test = { Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing" -Name "RebootPending" -ErrorAction Ignore } 850 | TestType = "ValueExists" 851 | } 852 | @{ 853 | Name = "RebootRequired" 854 | Test = { Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" -Name "RebootRequired" -ErrorAction Ignore } 855 | TestType = "ValueExists" 856 | } 857 | @{ 858 | Name = "PendingFileRenameOperations" 859 | Test = { Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name "PendingFileRenameOperations" -ErrorAction Ignore } 860 | TestType = "NonNullValue" 861 | } 862 | ) 863 | foreach ($test in $pendingRebootTests) { 864 | $result = Invoke-Command -ScriptBlock $test.Test 865 | if ($test.TestType -eq "ValueExists" -and $result) { 866 | return $true 867 | } elseif ($test.TestType -eq "NonNullValue" -and $result -and $result.($test.Name)) { 868 | return $true 869 | } else { 870 | return $false 871 | } 872 | } 873 | } 874 | 875 | function Update-Repo { 876 | <# 877 | .SYNOPSIS 878 | Update the repository if enough time has passed since the previous "git pull" 879 | #> 880 | [CmdletBinding(SupportsShouldProcess)] 881 | [OutputType([bool])] 882 | param( 883 | [TimeSpan]$MaxTimeSpan = (New-TimeSpan -Days 1) 884 | ) 885 | if (! ($RepoInUserDir -or (Test-Admin))) { 886 | Show-Output "Cannot update the repo, as the script is not elevated (yet)." 887 | return 888 | } 889 | $FetchHeadPath = "${RepoPath}\.git\FETCH_HEAD" 890 | if (!(Test-Path "${FetchHeadPath}")) { 891 | Show-Output "The date of the previous `"git pull`" could not be determined. Updating." 892 | if($PSCmdlet.ShouldProcess($RepoPath, "git pull")) { 893 | git pull 894 | } 895 | Show-Output "You may have to restart the script to use the new version." 896 | # return $true 897 | return 898 | } 899 | $LastPullTime = (Get-Item "${FetchHeadPath}").LastWriteTime 900 | $TimeDifference = New-TimeSpan -Start $LastPullTime -End (Get-Date) 901 | if ($TimeDifference -lt $MaxTimeSpan) { 902 | Show-Output "Previous `"git pull`" was run on ${LastPullTime}. No need to update." 903 | # return $false 904 | } else { 905 | Show-Output "Previous `"git pull`" was run on ${LastPullTime}. Updating." 906 | if ($RepoInUserDir -and (! $Elevated)) { 907 | $GitConfigPath = "${env:UserProfile}\.gitconfig" 908 | $RepoName = Split-Path "${RepoPath}" -Leaf 909 | if ((! (Test-Path "${GitConfigPath}")) -or (! (Select-String "${GitConfigPath}" -Pattern "${RepoName}" -SimpleMatch))) { 910 | Show-Output "Git config was not found, or it did not contain the repository path. Adding ${RepoPath} as a safe directory." 911 | if($PSCmdlet.ShouldProcess($RepoPath, "git config")) { 912 | git config --global --add safe.directory "${RepoPath}" 913 | } 914 | } 915 | } 916 | if($PSCmdlet.ShouldProcess($RepoPath, "git pull")) { 917 | git pull 918 | } 919 | Show-Output "You may have to restart the script to use the new version." 920 | # return $true 921 | } 922 | } 923 | -------------------------------------------------------------------------------- /Install-Software.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Install all the software and configuration usually needed for a computer. 4 | .PARAMETER Elevated 5 | This parameter is for internal use to check whether an UAC prompt has already been attempted. 6 | #> 7 | 8 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Elevated", Justification="Used in utils")] 9 | param( 10 | [switch]$Elevated 11 | ) 12 | 13 | ##### 14 | # Script startup 15 | ##### 16 | 17 | . "${PSScriptRoot}\Utils.ps1" 18 | 19 | if ($RepoInUserDir) { 20 | Update-Repo 21 | } 22 | # This should be as early as possible to avoid loading the function definitions etc. twice. 23 | Elevate($myinvocation.MyCommand.Definition) 24 | 25 | Start-Transcript -Path "${LogPath}\install-software_$(Get-Date -Format "yyyy-MM-dd_HH-mm").txt" 26 | if (! $RepoInUserDir) { 27 | Update-Repo 28 | } 29 | 30 | # Startup info 31 | $host.ui.RawUI.WindowTitle = "Mika's computer installation script" 32 | Show-Output -ForegroundColor Cyan "Starting Mika's computer installation script." 33 | Show-Output -ForegroundColor Cyan "If some installer requests a reboot, select no, and only reboot the computer when the installation script is ready." 34 | Request-DomainConnection 35 | Show-Output -ForegroundColor Cyan "The graphical user interface (GUI) is a very preliminary version and will be improved in the future." 36 | Show-Output -ForegroundColor Cyan "If it doesn't fit on your monitor, please reduce the display scaling at:" 37 | Show-Output -ForegroundColor Cyan "`"Settings -> System -> Display -> Scale and layout -> Change the size of text, apps and other items`"" 38 | 39 | # Startup tasks 40 | Add-ScriptShortcuts 41 | Set-RepoPermissions 42 | Test-PendingRebootAndExit 43 | 44 | if ($env:ComputerName -eq "agx-z2e-win") { 45 | Install-Module DisplayConfig 46 | } 47 | 48 | # Global variables 49 | $GlobalHeight = 800; 50 | $GlobalWidth = 700; 51 | $SoftwareRepoPath = "V:\IT\Software" 52 | $ComputerSystem = Get-CimInstance -ClassName Win32_ComputerSystem 53 | 54 | ##### 55 | # Installer definitions 56 | ##### 57 | 58 | # TODO: hide non-work-related apps on domain computers 59 | $ChocoPrograms = [ordered]@{ 60 | "7-Zip" = "7zip", "File compression utility"; 61 | "ActivityWatch" = "activitywatch", "Time management utility"; 62 | "Adobe Acrobat Reader DC" = "adobereader", "PDF reader. Not usually needed, as web browsers have good integrated pdf readers."; 63 | "AltSnap" = "altsnap", "For moving windows easily"; 64 | "Anaconda 3 (NOTE!)" = "anaconda3", "NOTE! Comes with lots of libraries. Use Miniconda or regular Python instead, unless you absolutely need this. Installation with Chocolatey does not work with PyCharm without custom symlinks."; 65 | "Android Debug Bridge" = "adb", "For developing Android applications"; 66 | "BleachBit" = "bleachbit", "Utility for freeing disk space"; 67 | "BOINC" = "boinc", "Distributed computing platform"; 68 | "Chocolatey GUI (RECOMMENDED!)" = "chocolateygui", "Graphical user interface for managing packages installed with this script and for installing additional software."; 69 | "CLion" = "clion-ide", "C/C++ IDE, commercial use requires a license"; 70 | "CMake" = "cmake", "C/C++ make utility"; 71 | "Discord" = "discord", "Chat and group call platform"; 72 | "DisplayCal" = "displaycal", "Display calibration utility"; 73 | "Docker Desktop (NOTE!)" = "docker-desktop", "Container platform. NOTE! Windows Subsystem for Linux 2 (WSL 2) has to be installed before installing this"; 74 | "EA App" = "ea-app", "Game store"; 75 | "eDrawings Viewer" = "edrawings-viewer", "2D & 3D CAD viewer"; 76 | "Epic Games Launcher" = "epicgameslauncher", "Game store"; 77 | "Firefox" = "firefox", "Web browser"; 78 | "GIMP" = "gimp", "Image editor"; 79 | # "Git" = "git", "Version control"; 80 | "GNU Octave" = "octave", "Free MATLAB alternative"; 81 | "Gpg4Win" = "gpg4win", "GPG / PGP software for encryption and signing"; 82 | "ImageJ" = "imagej", "Image processing and analysis software"; 83 | "Inkscape" = "inkscape", "Vector graphics editor"; 84 | "Intel Driver & Support Assistant" = "intel-dsa", "Intel driver updater"; 85 | "Java Runtime (JRE) 8" = "javaruntime", "Required for running Java-based applications"; 86 | "Jellyfin" = "jellyfin-media-player", "Jellyfin client for playing media from a self-hosted server"; 87 | "KeePassXC" = "keepassxc", "Password manager"; 88 | "KLayout" = "klayout", "Lithography mask editor"; 89 | "Kingston SSD Manager" = "kingston-ssd-manager", "Management tool and firmware updater for Kingston SSDs"; 90 | "LibreOffice" = "libreoffice-fresh", "Office suite"; 91 | "Logi Options+" = "logioptionsplus", "Driver for logitech input devices"; 92 | "Logitech Gaming Software" = "logitechgaming", "Driver for old Logitech gaming input devices"; 93 | "Logitech G Hub" = "lghub", "Driver for new Logitech gaming input devices"; 94 | "Mattermost" = "mattermost-desktop", "Messaging app for teams and organizations (open source Slack alternative)"; 95 | "Microsoft Teams" = "microsoft-teams", "Instant messaging and video conferencing platform"; 96 | "MiKTeX" = "miktex", "LaTeX environment"; 97 | # Minecraft should be installed from the Microsoft Store to enable automatic updates. 98 | # "Minecraft" = "minecraft-launcher", "The classic sandbox game"; 99 | "Miniconda 3 (NOTE!)" = "miniconda3", "Anaconda package manager and Python 3 without the pre-installed libraries. NOTE! Installation with Chocolatey does not work with PyCharm without custom symlinks."; 100 | "mRemoteNG" = "mremoteng", "Remote connections manager (RDP, VNC, SSH etc.)"; 101 | "Mumble" = "mumble", "Group call platform"; 102 | "Notepad++" = "notepadplusplus", "Text editor"; 103 | "NVM" = "nvm", "Node.js Version Manager"; 104 | "Obsidian" = "obsidian", "A note-taking app"; 105 | "OBS Studio" = "obs-studio", "Screen capture and broadcasting utility"; 106 | "OpenVPN" = "openvpn", "VPN client"; 107 | "PDFsam" = "pdfsam", "PDF Split & Merge utility"; 108 | "PDF-XChange Editor" = "pdfxchangeeditor", "PDF editor"; 109 | "PDF Arranger" = "pdfarranger", "PDF split & merge utility"; 110 | "pgAdmin" = "pgadmin4", "Graphical user interface (GUI) for managing PostgreSQL"; 111 | "Plex" = "plex", "Plex client for playing media from a self-hosted server"; 112 | "Plexamp" = "plexamp", "Plex client for playing music from a self-hosted server"; 113 | "PostgreSQL" = "postgresql", "Database for e.g. web app development"; 114 | "PowerToys" = "powertoys", "Various utilities for Windows"; 115 | "PuTTY" = "putty", "SSH, Telnet and serial port terminal client"; 116 | "PyCharm Community" = "pycharm-community", "Python IDE"; 117 | "PyCharm Professional" = "pycharm", "Professional Python IDE, requires a license"; 118 | "Python (NOTE!)" = "python", "NOTE! This will be updated automatically in the future, which may break installed libraries and virtualenvs."; 119 | "qBittorrent" = "qbittorrent", "Torrent client"; 120 | "Raspberry Pi Imager" = "rpi-imager", "A tool for formatting SD cards for Raspberry Pi"; 121 | "RescueTime" = "rescuetime", "Time management utility, requires a license"; 122 | "Rufus" = "rufus", "Creates USB installers for operating systems"; 123 | "RustRover" = "rustrover", "Rust IDE, commercial use requires a license"; 124 | "Samsung Magician" = "samsung-magician", "Samsung SSD management software"; 125 | "Signal" = "signal", "Secure instant messenger"; 126 | "Slack" = "slack", "Messaging app for teams and organizations"; 127 | "SpaceSniffer" = "spacesniffer", "See what's consuming the hard disk space"; 128 | "Speedtest CLI" = "speedtest", "Command-line utility for measuring internet speed"; 129 | "Spotify" = "spotify", "Music streaming service client"; 130 | "Steam" = "steam", "Game store"; 131 | "Stream Deck" = "streamdeck", "Control software for Elgato Stream Decks"; 132 | "Syncthing / SyncTrayzor" = "synctrayzor", "Utility for directly synchronizing files between devices"; 133 | "TeamViewer" = "teamviewer", "Remote control utility, commercial use requires a license"; 134 | "Telegram" = "telegram", "Instant messenger"; 135 | "Texmaker" = "texmaker", "LaTeX editor"; 136 | "Thunderbird" = "thunderbird", "Email client"; 137 | "Tidal" = "tidal", "Music streaming service client"; 138 | "TightVNC" = "tightvnc", "VNC server. Can be used with noVNC by using the configuration from Mika's GitHub."; 139 | "Ubisoft Connect" = "ubisoft-connect", "Game store"; 140 | "Ventoy" = "ventoy", "A tool for creating bootable USB drives"; 141 | "VirtualBox (NOTE!)" = "virtualbox", "Virtualization platform. NOTE! Cannot be installed on the same computer as Hyper-V. Hardware virtualization should be enabled in BIOS/UEFI before installing."; 142 | "VirtualBox Guest Additions (NOTE!)" = "virtualbox-guest-additions-guest.install", "NOTE! This should be installed inside a virtual machine."; 143 | "VLC" = "vlc", "Video player"; 144 | "Visual Studio Code (NOTE!)" = "vscode", "Text editor / IDE. Sends lots of tracking data to Microsoft. Use VSCodium instead."; 145 | "VSCodium" = "vscodium", "Text editor / IDE. Open source version of Visual Studio Code."; 146 | "Wacom drivers" = "wacom-drivers", "Drivers for Wacom drawing tablets"; 147 | "Xournal++" = "xournalplusplus", "For taking handwritten notes with a drawing tablet"; 148 | "Xerox Global Print Driver PCL/PS V3" = "xeroxupd", "Driver for Xerox printers"; 149 | "yt-dlp" = "yt-dlp", "Video downloader for e.g. YouTube"; 150 | "YubiKey Manager" = "yubikey-manager", "Management software for the YubiKey hardware security keys"; 151 | "Zoom" = "zoom", "Video conferencing"; 152 | "Zotero" = "zotero", "Reference and citation management software"; 153 | } 154 | $WingetPrograms = [ordered]@{ 155 | "PowerShell" = "Microsoft.PowerShell", "The new cross-platform PowerShell (>= 7)"; 156 | # The PowerToys version available from WinGet is a preview. 157 | # https://github.com/microsoft/PowerToys#via-winget-preview 158 | # "PowerToys" = "Microsoft.PowerToys"; 159 | } 160 | $WindowsCapabilities = [ordered]@{ 161 | # "OpenSSH client" = "OpenSSH.Client~~~~0.0.1.0", "NOTE! This is an old version that does not support FIDO2. Install SSH from the other programs menu instead."; 162 | "RSAT AD LDS (Active Directory management tools)" = "Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0", "Active Directory management tools"; 163 | "RSAT BitLocker tools" = "Rsat.BitLocker.Recovery.Tools~~~~0.0.1.0", "Active Directory BitLocker management tools"; 164 | "RSAT DHCP tools" = "Rsat.DHCP.Tools~~~~0.0.1.0", "Active Directory DHCP management tools"; 165 | "RSAT DNS tools" = "Rsat.Dns.Tools~~~~0.0.1.0", "Active Directory DNS management tools"; 166 | "RSAT cluster tools" = "Rsat.FailoverCluster.Management.Tools~~~~0.0.1.0", "Server cluster management tools"; 167 | "RSAT file server tools" = "Rsat.FileServices.Tools~~~~0.0.1.0", "Active Directory file server management tools"; 168 | "RSAT group policy tools" = "Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0", "Active Directory group policy management tools"; 169 | # "RSAT network controller tools" = "Rsat.NetworkController.Tools~~~~0.0.1.0", "RSAT network controller tools"; 170 | "RSAT Server Manager" = "Rsat.ServerManager.Tools~~~~0.0.1.0", "Remote server management tools"; 171 | "RSAT Shielded VM tools" = "Rsat.Shielded.VM.Tools~~~~0.0.1.0", "Management tools for shielded virtual machines"; 172 | "RSAT WSUS tools" = "Rsat.WSUS.Tools~~~~0.0.1.0", "Active Directory Windows Update management tools"; 173 | "SNMP client" = "SNMP.Client~~~~0.0.1.0", "SNMP remote monitoring client"; 174 | } 175 | $WindowsFeatures = [ordered]@{ 176 | "Hyper-V (NOTE!)" = "Microsoft-Hyper-V-All", "Virtualization platform. NOTE! Cannot be installed on the same computer as VirtualBox. Hardware virtualization should be enabled in BIOS/UEFI before installing."; 177 | "Hyper-V management tools" = "Microsoft-Hyper-V-Tools-All", "Tools for managing Hyper-V servers, both local and remote"; 178 | } 179 | 180 | # Installer functions 181 | 182 | function Install-AtostekID([string]$Version = "4.4.0.0") { 183 | <# 184 | .LINK 185 | https://dvv.fi/en/card-reader-software 186 | #> 187 | # Get-Package does not work on PowerShell 7 188 | $DigiSign = Get-InstalledSoftware | Where-Object {$_.Name -eq "mPollux DigiSign Client"} 189 | if ($DigiSign) { 190 | Show-Output "Fujitsu mPollux DigiSign Client found. Uninstalling." 191 | Invoke-CimMethod -InputObject $DigiSign -MethodName Uninstall 192 | } else { 193 | Show-Output "Fujitsu mPollux DigiSign Client was not found, so there's no need to uninstall it before installing Atostek ID." 194 | } 195 | $Filename = "AtostekID_WIN_${Version}.msi" 196 | Install-FromUri -Name "Atostek ID" -Uri "https://files.fineid.fi/download/atostek/${Version}/windows/${Filename}" -Filename "${Filename}" 197 | } 198 | 199 | function Install-BaslerPylon([string]$Version = "25.10.2") { 200 | <# 201 | .LINK 202 | https://www.baslerweb.com/en/downloads/software/ 203 | #> 204 | $Filename = "Basler pylon ${Version}.exe" 205 | $FilenameUrl = [uri]::EscapeDataString($Filename) 206 | Install-FromUri -Name "Basler Pylon Camera Software Suite" -Uri "https://downloadbsl.blob.core.windows.net/software/pylon%20${Version}/${FilenameUrl}" -Filename "${Filename}" 207 | } 208 | 209 | function Install-CorelDRAW { 210 | <# 211 | .LINK 212 | https://myaccount.corel.com/perpetual-products 213 | .LINK 214 | https://www.coreldraw.com/en/licensing/download/ 215 | #> 216 | Install-FromUri -Name "CorelDRAW" -Uri "https://www.corel.com/akdlm/6763/downloads/free/trials/GraphicsSuite/22H1/JL83s3fG/CDGS.exe" -Filename "CDGS.exe" 217 | } 218 | 219 | # function Install-DigiSign([string]$Version = "4.3.0(8707)") { 220 | # $Filename = "DigiSignClient_for_dvv_${Version}.exe" 221 | # Install-FromUri -Name "Fujitsu mPollux DigiSign" -Uri "https://dvv.fi/documents/16079645/216375523/${Filename}" -Filename "${Filename}" 222 | # } 223 | 224 | function Install-DigilentWaveforms([string]$Version = "3.24.4") { 225 | <# 226 | .LINK 227 | https://cloud.digilent.com/myproducts/waveforms 228 | #> 229 | $Filename = "digilent.waveforms_v${Version}_64bit.exe" 230 | Install-FromUri -Name "Digilent Waveforms" -Uri "https://files.digilent.com/Software/Waveforms/${Version}/${Filename}" -Filename "${Filename}" 231 | } 232 | 233 | function Install-Eduroam { 234 | <# 235 | .LINK 236 | https://www.eduroam.app/ 237 | #> 238 | Install-FromUri -Name "Eduroam" -Uri "https://dl.eduroam.app/windows/x86_64/geteduroam.exe" -Filename "geteduroam.exe" 239 | } 240 | 241 | function Install-FDAeSubmitter { 242 | <# 243 | .LINK 244 | https://www.fda.gov/industry/fda-esubmitter/esubmitter-download-and-installation 245 | #> 246 | Install-FromUri -Name "FDA eSubmitter" ` 247 | -Uri "https://www.accessdata.fda.gov/esubmissions/ftparea/esubmitter/platforms/Windows/IncludeJvm/jinstall.zip" ` 248 | -Filename "jinstall.zip" -UnzipFolderName "${jinstall}" -UnzippedFilePath "jinstall.exe" ` 249 | -SHA256 "b98d19c7ae5daf53f7cb7e552a15f8c5c67609a2ffb4b789cf530f8b6733b6fb" ` 250 | -BypassAuthenticode 251 | } 252 | 253 | function Install-Git { 254 | <# 255 | .SYNOPSIS 256 | Install Git with Chocolatey and custom parameters 257 | .LINK 258 | https://github.com/chocolatey-community/chocolatey-packages/blob/master/automatic/git.install/ARGUMENTS.md 259 | #> 260 | # if (Get-WindowsCapability -Online -Name "OpenSSH.Client~~~~0.0.1.0") { 261 | # Show-Output "Found Windows integrated OpenSSH client, which the SSH included in Git for Windows should override. Removing." 262 | Remove-WindowsCapability -Online -Name "OpenSSH.Client~~~~0.0.1.0" 263 | # } 264 | Show-Output "Installing Git with Chocolatey and custom parameters" 265 | choco upgrade git.install -y --force --params "/GitAndUnixToolsOnPath /WindowsTerminalProfile" 266 | } 267 | 268 | function Install-IDSPeak ([string]$Version = "2.18.1.0", [string]$Version2 = "183") { 269 | <# 270 | .LINK 271 | https://en.ids-imaging.com/download-peak.html 272 | #> 273 | $Folder = "ids-peak-win-extended-setup-64-${Version}" 274 | $Filename = "${Folder}.zip" 275 | Install-FromUri -Name "IDS Peak" -Uri "https://en.ids-imaging.com/files/downloads/ids-peak/software/windows/${Filename}" -Filename "${Filename}" -UnzipFolderName "${Folder}" -UnzippedFilePath "ids_peak_${Version}-${Version2}_full_with_ueye_runtime.exe" 276 | } 277 | 278 | function Install-IDSSoftwareSuite ([string]$Version = "4.95.2", [string]$Version2 = "49520") { 279 | $Folder = "ids-software-suite-win-${Version}" 280 | $Filename = "${Folder}.zip" 281 | Install-FromUri -Name "IDS Software Suite (µEye)" -Uri "https://en.ids-imaging.com/files/downloads/ids-software-suite/software/windows/${Filename}" -Filename "${Filename}" -UnzipFolderName "${Folder}" -UnzippedFilePath "uEye_${Version2}.exe" 282 | } 283 | 284 | function Install-LabVIEWRuntime ([string]$Version = "25.3") { 285 | $Year = $Version.SubString(0,2) 286 | $Filename ="ni-labview-20${Year}-runtime-engine_${Version}_online.exe" 287 | Install-FromUri -Name "LabVIEW Runtime" -Uri "https://download.ni.com/support/nipkg/products/ni-l/ni-labview-20${Year}-runtime-engine/${Version}/online/${Filename}" -Filename "${Filename}" 288 | } 289 | 290 | function Install-LabVIEWRuntime2014SP1 { 291 | <# 292 | .SYNOPSIS 293 | Install LabVIEW Runtime 2014 SP1 32-bit 294 | .LINK 295 | https://www.ni.com/en/support/downloads/software-products/download.labview-runtime.html#306243 296 | .DESCRIPTION 297 | Required for SSMbe 298 | #> 299 | $Folder = "LVRTE2014SP1_f11Patchstd" 300 | $Filename = "${Folder}.zip" 301 | Install-FromUri -Name "LabVIEW Runtime 2014 SP1 32-bit" -Uri "https://download.ni.com/support/softlib/labview/labview_runtime/2014%20SP1/Windows/f11/${Filename}" -Filename "${Filename}" -UnzipFolderName "${Folder}" -UnzippedFilePath "setup.exe" -SHA256 "2c54ab5169dd0cc9f14a7b0057881207b6f76e065c7c78bb8c898ac9c5ca0831" 302 | } 303 | 304 | function Install-LenovoSuperIOFirmware { 305 | [OutputType([int])] 306 | param() 307 | # Lenovo ThinkStation P330 Tiny 308 | if ($ComputerSystem.Model -eq "30CES0B200") { 309 | $InstallerPath = "${SoftwareRepoPath}\Lenovo\P330 Tiny\Firmware\Lenovo Super IO Firmware\m1uct18usa.exe" 310 | $ScriptDir = "${env:SystemDrive}\SWTOOLS\FLASH\M1UCT18USA" 311 | $ScriptPath = "${ScriptDir}\Flash64.cmd" 312 | if (Test-Path "$InstallerPath") { 313 | Start-Process -NoNewWindow -Wait "$InstallerPath" -ArgumentList "/silent" 314 | if (! (Get-YesNo "Did you see the firmware being updated in addition to the flasher being installed?")) { 315 | Show-Output "Since the firmware was not yet updated, updating it now." 316 | Start-Process -NoNewWindow -Wait "cmd.exe" -ArgumentList "/c","cd ${ScriptDir} && ${ScriptPath}" 317 | } 318 | } else { 319 | Show-Output "The Lenovo Super IO firmware installer was not found. Is the network drive mounted?" 320 | return 1 321 | } 322 | } else { 323 | Show-Output -ForegroundColor Red "No Lenovo Super IO firmware has been configured for your computer model `"$ComputerSystem.Model`"." 324 | return 1 325 | } 326 | return 0 327 | } 328 | 329 | function Install-MeerstetterTEC { 330 | <# 331 | .SYNOPSIS 332 | Install Meerstetter TEC Software 333 | .LINK 334 | https://www.meerstetter.ch/customer-center/downloads/category/31-latest-software 335 | #> 336 | # Spaces are not allowed in msiexec filenames. Please also see this issue: 337 | # https://stackoverflow.com/questions/10108517/what-can-cause-msiexec-error-1619-this-installation-package-could-not-be-opened 338 | Install-FromUri -Name "Meerstetter TEC software" -Uri "https://www.meerstetter.ch/customer-center/downloads/category/31-latest-software?download=764:tec-controller-software" -Filename "TEC_Software.msi" 339 | } 340 | 341 | function Install-MEFirmware { 342 | [OutputType([int])] 343 | param() 344 | # Lenovo ThinkStation P330 Tiny 345 | if ($ComputerSystem.Model -eq "30CES0B200") { 346 | $FilePath = "${SoftwareRepoPath}\Lenovo\P330 Tiny\Firmware\Intel ME Firmware\12.0.90.2072_corporate.exe" 347 | if (Test-Path "$FilePath") { 348 | Start-Process -NoNewWindow -Wait "$FilePath" -ArgumentList "/silent" 349 | } else { 350 | Show-Output "The Intel ME firmware installer was not found. Is the network drive mounted?" 351 | return 1 352 | } 353 | } else { 354 | Show-Output -ForegroundColor Red "No Intel ME firmware has been configured for your computer model `"$ComputerSystem.Model`"." 355 | return 1 356 | } 357 | return 0 358 | } 359 | 360 | function Install-NI4882 ([string]$Version = "25.8") { 361 | <# 362 | .SYNOPSIS 363 | Install National Instruments NI-488.2 364 | .LINK 365 | https://www.ni.com/fi-fi/support/downloads/drivers/download.ni-488-2.html 366 | #> 367 | $Filename = "ni-488.2_${Version}_online.exe" 368 | Install-FromUri -Name "NI 488.2 (GPIB) drivers" -Uri "https://download.ni.com/support/nipkg/products/ni-4/ni-488.2/${Version}/online/${Filename}" -Filename "${Filename}" 369 | } 370 | 371 | function Install-NI-VISA1401Runtime { 372 | <# 373 | .SYNOPSIS 374 | Install NI-VISA 14.0.1 Runtime 375 | .LINK 376 | https://www.ni.com/en/support/downloads/drivers/download.ni-visa.html#306102 377 | .DESCRIPTION 378 | Required for SSMbe. 379 | LabVIEW (runtime) should be installed first, 380 | but this is automatically the case when installing both at the same time with this script, 381 | since LabVIEW comes alphabetically first. 382 | #> 383 | $Folder = "NIVISA1401runtime" 384 | $Filename = "${Folder}.zip" 385 | Install-FromUri -Name "NI-VISA 14.0.1 Runtime" -Uri "https://download.ni.com/support/softlib/visa/VISA%20Run-Time%20Engine/14.0.1/${Filename}" -Filename "${Filename}" -UnzipFolderName "${Folder}" -UnzippedFilePath "setup.exe" -SHA256 "960e0f68ab7dbff286ba8ac2286d4e883f02f9390c2a033b433005e29fb93e72" 386 | } 387 | 388 | function Install-OpenVPN { 389 | <# 390 | .SYNOPSIS 391 | Install OpenVPN Community 392 | .LINK 393 | https://openvpn.net/community-downloads/ 394 | #> 395 | [OutputType([int])] 396 | param( 397 | [string]$Version = "2.6.15", 398 | [string]$Version2 = "I001" 399 | ) 400 | $Arch = Get-InstallBitness -x86 "x86" -x86_64 "amd64" 401 | $Filename = "OpenVPN-${Version}-${Version2}-${Arch}.msi" 402 | Install-FromUri -Name "OpenVPN" -Uri "https://swupdate.openvpn.org/community/releases/$Filename" -Filename "${Filename}" 403 | } 404 | 405 | function Install-OriginLab { 406 | <# 407 | Install the full version of OriginLab 408 | #> 409 | [OutputType([int])] 410 | param( 411 | [string]$Version = "Origin2022bSr1No_H" 412 | ) 413 | return Install-Executable -Name "OriginLab" -Path "${SoftwareRepoPath}\Origin\${Version}\setup.exe" 414 | } 415 | 416 | function Install-OriginViewer { 417 | <# 418 | .SYNOPSIS 419 | Install Origin Viewer, the free viewer for Origin data visualization and analysis files 420 | .LINK 421 | https://www.originlab.com/viewer/ 422 | #> 423 | [OutputType([bool])] 424 | param() 425 | Show-Output "Downloading Origin Viewer" 426 | $Arch = Get-InstallBitness -x86 "" -x86_64 "_64" 427 | $Filename = "OriginViewer${Arch}.zip" 428 | Invoke-WebRequestFast -Uri "https://www.originlab.com/ftp/${Filename}" -OutFile "${Downloads}\${Filename}" 429 | $DestinationPath = "${Home}\Downloads\Origin Viewer" 430 | if(-Not (Clear-Path "${DestinationPath}")) { 431 | return $false 432 | } 433 | Show-Output "Extracting Origin Viewer" 434 | Expand-Archive -Path "${Downloads}\$Filename" -DestinationPath "${DestinationPath}" 435 | $ExePath = Find-First -Filter "*.exe" -Path "${DestinationPath}" 436 | if ($null -eq $ExePath) { 437 | Show-Output -ForegroundColor Red "No exe file was found in the extracted directory." 438 | return $false 439 | } 440 | New-Shortcut -Path "${env:APPDATA}\Microsoft\Windows\Start Menu\Programs\Origin Viewer.lnk" -TargetPath "${ExePath}" 441 | return $true 442 | } 443 | 444 | function Install-Rezonator1([string]$Version = "1.7.116.375", [string]$SHA256 = "c0601dd4de38de638f90ef9314c5d6f5a808cf5dd848f4a02ac7382b71d43ad6") { 445 | <# 446 | .SYNOPSIS 447 | Install the reZonator laser cavity simulator 448 | .LINK 449 | http://rezonator.orion-project.org/?page=dload 450 | #> 451 | $Filename = "rezonator-${Version}.exe" 452 | # The reZonator web page does not support HTTPS. 453 | Install-FromUri -Name "reZonator 1" -Uri "https://rezonator.orion-project.org/files/${Filename}" ` 454 | -Filename "${Filename}" -SHA256 "${SHA256}" -BypassAuthenticode 455 | } 456 | 457 | function Install-Rezonator2 { 458 | <# 459 | .SYNOPSIS 460 | Install the reZonator 2 laser cavity simulator 461 | .LINK 462 | http://rezonator.orion-project.org/?page=dload 463 | #> 464 | [OutputType([bool])] 465 | param( 466 | [string]$Version = "2.1.1" 467 | ) 468 | Show-Output "Downloading reZonator 2" 469 | $Bitness = Get-InstallBitness -x86 "x32" -x86_64 "x64" 470 | $Filename = "rezonator-${Version}-win-${Bitness}.zip" 471 | Invoke-WebRequestFast -Uri "https://github.com/orion-project/rezonator2/releases/download/v${Version}/${Filename}" -OutFile "${Downloads}\${Filename}" 472 | $DestinationPath = "${Home}\Downloads\reZonator" 473 | if(-Not (Clear-Path "${DestinationPath}")) { 474 | return $false 475 | } 476 | Show-Output "Extracting reZonator 2" 477 | Expand-Archive -Path "${Downloads}\$Filename" -DestinationPath "${DestinationPath}" 478 | New-Shortcut -Path "${env:APPDATA}\Microsoft\Windows\Start Menu\Programs\reZonator 2.lnk" -TargetPath "${DestinationPath}\rezonator.exe" 479 | return $true 480 | } 481 | 482 | function Install-SMCThermoChiller ([string]$Version = "2.0.1.0", [string]$SHA256 = "cbb67f4b4e185c708c7dec523087b156c74eab816c136dbbeb1dd7f7dc1d5cb3") { 483 | <# 484 | .SYNOPSIS 485 | Monitoring software for SMC ThermoChillers, especially the HRR series 486 | .LINK 487 | https://smc-fluidcontrol.com/hrr-software/ 488 | #> 489 | $Folder = "hrr-v${Version}" 490 | $Filename = "${Folder}.zip" 491 | Install-FromUri -Name "SMC ThermoChiller" ` 492 | -Uri "https://static.smc.eu/binaries/content/assets/smc_global/products/engineering-tools/hrr-monitoring-software/${Filename}" ` 493 | -Filename "${Filename}" -UnzipFolderName "${Folder}" -UnzippedFilePath "HRR V${Version}.exe" -SHA256 "${SHA256}" -BypassAuthenticode 494 | } 495 | 496 | function Install-SNLO ([string]$Version = "78", [string]$SHA256 = "be4635f51f6d6f51433c660dc4787a256796fb9d35425605f212ff1a60aeba0a") { 497 | $Filename = "SNLO-v${Version}.exe" 498 | Install-FromUri -Name "SNLO" -Uri "https://as-photonics.com/snlo_files/${Filename}" -Filename "${Filename}" -SHA256 "${SHA256}" -BypassAuthenticode 499 | } 500 | 501 | function Install-SSMbe ([string]$Version = "20160525") { 502 | $FolderName = "SSMbe_exe_20160525" 503 | $FilePath = "${SoftwareRepoPath}\SS10-1 MBE software\${FolderName}.zip" 504 | $DestinationPath = "${Downloads}\SS10-1 MBE software" 505 | if (-Not (Clear-Path "${DestinationPath}")) { 506 | return $false 507 | } 508 | if (Test-Path $FilePath) { 509 | Expand-Archive -Path "${FilePath}" -DestinationPath "${DestinationPath}" 510 | } else { 511 | Show-Output -ForegroundColor Red "SSMbe installer was not found." 512 | } 513 | New-Shortcut -Path "${env:APPDATA}\Microsoft\Windows\Start Menu\Programs\SSMbe.lnk" -TargetPath "${DestinationPath}\${FolderName}\SSMbe.exe" 514 | return $true 515 | } 516 | 517 | function Install-StarLab { 518 | <# 519 | .SYNOPSIS 520 | Install Ophir StarLab 521 | .LINK 522 | https://www.ophiropt.com/laser--measurement/software/starlab-for-usb 523 | #> 524 | $Filename="StarLab.zip" 525 | Install-FromUri -Name "Ophir StarLab" -Uri "https://www.ophiropt.com/mam/celum/celum_assets/op/resources/${Filename}" -Filename "${Filename}" -UnzipFolderName "StarLab" -UnzippedFilePath "StarLab_Setup.exe" 526 | } 527 | 528 | function Install-ThorCam ([string]$Version = "3.7.0.6") { 529 | <# 530 | .SYNOPSIS 531 | Install ThorLabs ThorCam 532 | .LINK 533 | https://www.thorlabs.com/software_pages/ViewSoftwarePage.cfm?Code=ThorCam 534 | #> 535 | Show-Output "Downloading Thorlabs ThorCam. The web server has strict bot detection and the download may therefore fail, producing an invalid file." 536 | $Arch = Get-InstallBitness -x86 "x86" -x86_64 "x64" 537 | $FilenameRemote = "Thorlabs%20Scientific%20Imaging%20Software%20${Arch}.exe" 538 | $FilenameLocal = "Thorlabs Scientific Imaging Software ${Arch}.exe" 539 | # The "FireFox" typo is by Microsoft itself. 540 | # $UserAgent = [Microsoft.PowerShell.Commands.PSUserAgent]::FireFox 541 | Invoke-WebRequestFast -Uri "https://www.thorlabs.com/software/THO/ThorCam/ThorCam_V${Version}/${FilenameRemote}" -OutFile "${Downloads}\${FilenameLocal}" # -UserAgent $UserAgent 542 | Show-Output "https://www.thorlabs.com/software/THO/ThorCam/ThorCam_V${Version}/${FilenameRemote}" 543 | Show-Output "Installing Thorlabs ThorCam" 544 | $Process = Start-Process -NoNewWindow -Wait -PassThru "${Downloads}\${FilenameLocal}" 545 | if ($Process.ExitCode -ne 0) { 546 | Show-Output -ForegroundColor Red "ThorCam installation seems to have failed. Probably the server detected that this is a script, resulting in a corrupted download. Please download ThorCam manually from the Thorlabs website." 547 | } 548 | } 549 | 550 | function Install-ThorlabsBeam ([string]$Version = "8.2.5232.395") { 551 | <# 552 | .SYNOPSIS 553 | Install Thorlabs Beam 554 | .LINK 555 | https://www.thorlabs.com/software_pages/ViewSoftwarePage.cfm?Code=Beam 556 | #> 557 | Show-Output "Downloading Thorlabs Beam. The web server has strict bot detection and the download may therefore fail, producing an invalid file." 558 | $Folder = "Thorlabs_Beam_${Version}" 559 | $Filename = "Thorlabs_Beam_${Version}.zip" 560 | Invoke-WebRequestFast -Uri "https://www.thorlabs.com/software/MUC/Beam/Software/Beam_${version}/${filename}" -OutFile "${Downloads}\${Filename}" 561 | Show-Output "Installing Thorlabs Beam" 562 | Expand-Archive -Path "${Downloads}\${Filename}" -DestinationPath "${Downloads}\${Folder}" 563 | $Process = Start-Process -NoNewWindow -Wait -PassThru "${Downloads}\${Folder}\Thorlabs Beam Setup.exe" 564 | if ($Process.ExitCode -ne 0) { 565 | Show-Output -ForegroundColor Red "Thorlabs Beam installation seems to have failed. Probably the server detected that this is a script, resulting in a corrupted download. Please download Thorlabs Beam manually from the Thorlabs website." 566 | } 567 | } 568 | 569 | function Install-ThorlabsElliptec ([string]$Version = "1.6.4") { 570 | <# 571 | .SYNOPSIS 572 | Install Thorlabs Elliptec software 573 | .LINK 574 | https://www.thorlabs.com/software_pages/ViewSoftwarePage.cfm?Code=ELL 575 | #> 576 | Show-Output "Downloading Thorlabs Beam. The web server has strict bot detection and the download may therefore fail, producing an invalid file." 577 | $Filename = "Thorlabs_Elliptec_${Version}.exe" 578 | Invoke-WebRequestFast -Uri "https://www.thorlabs.com/Software/Elliptec/Application/V${Version}/ELLO%20Install%20x64/setup.exe" -OutFile "${Downloads}\${Filename}" 579 | $Process = Start-Process -NoNewWindow -Wait -PassThru "${Downloads}\${Filename}" 580 | if ($Process.ExitCode -ne 0) { 581 | Show-Output -ForegroundColor Red "Thorlabs Elliptec installation seems to have failed. Probably the server detected that this is a script, resulting in a corrupted download. Please download Thorlabs Elliptec manually from the Thorlabs website." 582 | } 583 | } 584 | 585 | function Install-ThorlabsKinesis { 586 | <# 587 | .SYNOPSIS 588 | Install Thorlabs Kinesis 589 | .LINK 590 | https://www.thorlabs.com/software_pages/ViewSoftwarePage.cfm?Code=Motion_Control&viewtab=0 591 | #> 592 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Thorlabs Kinesis is a singular noun")] 593 | param( 594 | [string]$Version = "1.14.36", 595 | [string]$Version2 = "20973" 596 | ) 597 | Show-Output "Downloading Thorlabs Kinesis. The web server has strict bot detection and the download may therefore fail, producing an invalid file." 598 | $Arch = Get-InstallBitness -x86 "x86" -x86_64 "x64" 599 | $Filename = "kinesis_${Version2}_setup_${Arch}.exe" 600 | Invoke-WebRequestFast -Uri "https://www.thorlabs.com/Software/Motion%20Control/KINESIS/Application/v${Version}/${Filename}" -OutFile "${Downloads}\${Filename}" 601 | Show-Output "Installing Thorlabs Kinesis" 602 | $Process = Start-Process -NoNewWindow -Wait -PassThru "${Downloads}\${Filename}" 603 | if ($Process.ExitCode -ne 0) { 604 | Show-Output -ForegroundColor Red "Thorlabs Kinesis installation seems to have failed. Probably the server detected that this is a script, resulting in a corrupted download. Please download Thorlabs Kinesis manually from the Thorlabs website." 605 | } 606 | } 607 | 608 | function Install-VCU { 609 | [OutputType([int])] 610 | param( 611 | [string]$Version = "0.13.42" 612 | ) 613 | $FilePath = "${SoftwareRepoPath}\VCU\VCU_GUI_Setup_${Version}.exe" 614 | if (Test-Path "$FilePath") { 615 | Show-Output "Installing VCU GUI" 616 | Start-Process -NoNewWindow -Wait "$FilePath" 617 | } else { 618 | Show-Output "The VCU GUI installer was not found. Is the network drive mounted?" 619 | return 1 620 | } 621 | return 0 622 | } 623 | 624 | function Install-VeecoVision { 625 | [OutputType([int])] 626 | param() 627 | 628 | Show-Output "Searching for Veeco (Wyko) Vision from the network drive." 629 | $FilePath = "${SoftwareRepoPath}\Veeco\VISION64_V5.51_Release.zip" 630 | if (Test-Path "$FilePath") { 631 | Expand-Archive -Path "$FilePath" -DestinationPath "$Downloads" 632 | Show-Output "Installing Veeco (Wyko) Vision" 633 | Start-Process -NoNewWindow -Wait "${Downloads}\CD 775-425 SOFTWARE VISION64 V5.51\Install.exe" 634 | } else { 635 | Show-Output "Veeco (Wyko) Vision was not found. Is the network drive mounted?" 636 | Show-Output "It could be that your computer does not have the necessary group policies applied. Applying. You will need to reboot for the changes to become effective." 637 | gpupdate /force 638 | return 1 639 | } 640 | Show-Output "Searching for Veeco (Wyko) Vision update from the network drive." 641 | $FilePath = "${SoftwareRepoPath}\Veeco\Vision64 5.51 Update 3.zip" 642 | if (Test-Path "$FilePath") { 643 | Expand-Archive -Path "$FilePath" -DestinationPath "$Downloads" 644 | Show-Output "Installing Veeco (Wyko) Vision update" 645 | Start-Process -NoNewWindow -Wait "${Downloads}\Vision64 5.51 Update 3\CD\Vision64_5.51_Update_3.EXE" 646 | } else { 647 | Show-Output -ForegroundColor Red "Veeco (Wyko) Vision update was not found. Has the file been moved?" 648 | return 2 649 | } 650 | return 0 651 | } 652 | 653 | function Install-Wavesquared ([string]$Version = "4.4.2.25284") { 654 | # https://jrsoftware.org/ishelp/index.php?topic=setupcmdline 655 | Start-Process -NoNewWindow -Wait "${SoftwareRepoPath}\Imagine Optic\wavesquared_${Version}\WaveSuite_setup.exe" 656 | } 657 | 658 | function Install-WithSecure { 659 | $FilePath = "${SoftwareRepoPath}\WithSecure\ElementsAgentInstaller*.exe" 660 | if (Test-Path "${FilePath}") { 661 | Show-Output "Installing WithSecure Elements Agent" 662 | Start-Process -NoNewWindow -Wait "${FilePath}" 663 | } else { 664 | Show-Output -ForegroundColor Red "WithSecure Elements Agent installer was not found. Has the file been moved?" 665 | return 1 666 | } 667 | return 0 668 | } 669 | 670 | function Install-WSL { 671 | if (Test-CommandExists "wsl") { 672 | Show-Output "Installing Windows Subsystem for Linux (WSL), version >= 2" 673 | wsl --install 674 | } else { 675 | Show-Output -ForegroundColor Red "The installer command for Windows Subsystem for Linux (WSL) was not found. Are you running an old version of Windows?" 676 | } 677 | } 678 | 679 | function Install-Xeneth { 680 | [OutputType([int])] 681 | param() 682 | $Bitness = Get-InstallBitness -x86 "" -x86_64 "64" 683 | $FilePath = Resolve-Path "${SoftwareRepoPath}\Xenics\BOBCAT*\Software\Xeneth-Setup${Bitness}.exe" 684 | if (Test-Path "${FilePath}") { 685 | return Install-Executable -Name "Xeneth" -Path "${FilePath}" 686 | } else { 687 | Show-Output -ForegroundColor Red "Xeneth installer was not found. Has the file been moved?" 688 | } 689 | } 690 | 691 | $OtherOperations = [ordered]@{ 692 | "Atostek ID" = ${function:Install-AtostekID}, "Card reader software for Finnish identity cards"; 693 | "Basler Pylon" = ${function:Install-BaslerPylon}, "Driver for Basler cameras"; 694 | "CorelDRAW" = ${function:Install-CorelDRAW}, "Graphic design, illustration and technical drawing software. Requires a license."; 695 | "Digilent Waveforms" = ${function:Install-DigilentWaveforms}, "Measurement software for Digilent lab devices"; 696 | "Eduroam" = ${function:Install-Eduroam}, "University Wi-Fi"; 697 | "FDA eSubmitter" = ${function:Install-FDAeSubmitter}, "Utility for submitting information to the U.S. Food & Drug Administration"; 698 | # "Fujitsu mPollux DigiSign" = ${function:Install-DigiSign}, "Card reader software for Finnish identity cards"; 699 | "Geekbench" = ${function:Install-Geekbench}, "Performance testing utility, versions 2-5. Commercial use requires a license."; 700 | "Git" = ${function:Install-Git}, "Git with custom arguments (SSH available from PATH etc.)"; 701 | "IDS Peak" = ${function:Install-IDSPeak}, "Driver for IDS cameras and old Thorlabs cameras"; 702 | "IDS Software Suite (µEye, NOTE!)" = ${function:Install-IDSSoftwareSuite}, "Driver for old IDS/Thorlabs cameras. NOTE! IDS Peak should now be compatible also with these old cameras, so use it instead."; 703 | "Intel ME firmware" = ${function:Install-MEFirmware}, "Intel Management Engine firmware"; 704 | # "LabVIEW Runtime" = ${function:Install-LabVIEWRuntime}, "Required for running LabVIEW-based applications"; 705 | "LabVIEW Runtime 2014 SP1 32-bit" = ${function:Install-LabVIEWRuntime2014SP1}, "Required for SSMbe (it requires this specific older version instead of the latest)"; 706 | "Lenovo Super IO firmware" = ${function:Install-LenovoSuperIOFirmware}, "Firmware for the IO chip on Lenovo motherboards"; 707 | "Meerstetter TEC Software" = ${function:Install-MeerstetterTEC}, "Driver for Meerstetter TEC controllers"; 708 | "NI 488.2 (GPIB)" = ${function:Install-NI4882}, "National Instruments GPIB drivers. Includes NI-VISA."; 709 | "NI-VISA 14.0.1 Runtime" = ${function:Install-NI-VISA1401Runtime}, "Required for SSMbe (it requires this specific older version instead of the latest)"; 710 | # OpenVPN is also available from Chocolatey. 711 | # Use this manual version only when the package version in Chocolatey is too old. 712 | # "OpenVPN" = ${function:Install-OpenVPN}, "VPN client"; 713 | "Ophir StarLab" = ${function:Install-StarLab}, "Driver for Ophir power meters"; 714 | "OriginLab" = ${function:Install-OriginLab}, "OriginLab data graphing and analysis software"; 715 | "Origin Viewer" = ${function:Install-OriginViewer}, "Viewer for OriginLab data graphing and analysis files"; 716 | "Phoronix Test Suite" = ${function:Install-PTS}, "Performance testing framework"; 717 | "reZonator 1" = ${function:Install-Rezonator1}, "Simulator for optical cavities (old stable version)"; 718 | "reZonator 2" = ${function:Install-Rezonator2}, "Simulator for optical cavities (new beta version)"; 719 | "SMC ThermoChiller" = ${function:Install-SMCThermoChiller}, "Monitoring software for SMC ThermoChillers, especially the HRR series"; 720 | "SNLO" = ${function:Install-SNLO}, "Crystal nonlinear optics simulator"; 721 | "SSMbe (NOTE!)" = ${function:Install-SSMbe}, "Control software for the SS10-1 MBE reactor. NOTE! Also install the LabVIEW Runtime and NI-VISA dependencies."; 722 | "Thorlabs ThorCam (NOTE!)" = ${function:Install-ThorCam}, "Driver for Thorlabs cameras. NOTE! Use IDS Peak instead for old cameras."; 723 | "Thorlabs Beam" = ${function:Install-ThorlabsBeam}, "Driver for Thorlabs beam profilers and M2 measurement systems"; 724 | "Thorlabs Elliptec" = ${function:Install-ThorlabsElliptec}, "Driver for Thorlabs Elliptec stages and mounts"; 725 | "Thorlabs Kinesis" = ${function:Install-ThorlabsKinesis}, "Driver for Thorlabs motors and stages"; 726 | "VCU" = ${function:Install-VCU}, "VCU GUI"; 727 | "Veeco (Wyko) Vision" = ${function:Install-VeecoVision}, "Data analysis tool for Veeco/Wyko profilers"; 728 | "Wavesquared" = ${function:Install-Wavesquared}, "M2 factor analysis software"; 729 | "Windows Subsystem for Linux (WSL, NOTE!)" = ${function:Install-WSL}, "Compatibility layer for running Linux applications on Windows, version >= 2. Hardware virtualization should be enabled in BIOS/UEFI before installing."; 730 | "WithSecure Elements Agent" = ${function:Install-WithSecure}, "Anti-virus. Requires a license."; 731 | "Xeneth" = ${function:Install-Xeneth}, "Driver for Xenics cameras"; 732 | # These are the last on purpose 733 | "Maintenance" = "${PSScriptRoot}\Maintenance.ps1", "Run the maintenance script"; 734 | "Report" = "${PSScriptRoot}\Report.ps1", "Run the reporting script"; 735 | } 736 | 737 | ##### 738 | # GUI functions 739 | ##### 740 | 741 | # Function definitions should be after the loading of utilities 742 | function New-List { 743 | <# 744 | .SYNOPSIS 745 | Create a GUI element for selecting options from a list with checkboxes 746 | .LINK 747 | https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.checkedlistbox 748 | #> 749 | [OutputType([system.Windows.Forms.CheckedListBox])] 750 | param( 751 | [Parameter(mandatory=$true)][System.Object]$Parent, 752 | [Parameter(mandatory=$true)][String]$Title, 753 | [Parameter(mandatory=$true)][String[]]$Options, 754 | [int]$Width = $GlobalWidth 755 | ) 756 | # Title label 757 | $Label = New-Object System.Windows.Forms.Label 758 | $Label.Text = $Title; 759 | $Label.Width = $Width; 760 | $Parent.Controls.Add($Label); 761 | # Create a CheckedListBox 762 | $List = New-Object -TypeName System.Windows.Forms.CheckedListBox; 763 | $Parent.Controls.Add($List); 764 | $List.Items.AddRange($Options); 765 | $List.CheckOnClick = $true; 766 | $List.Width = $Width; 767 | $List.Height = $Options.Count * 17 + 18; 768 | return $List; 769 | } 770 | 771 | function New-Table { 772 | <# 773 | .SYNOPSIS 774 | Create a GUI element for selecting items from a list with checboxes 775 | .LINK 776 | https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.datagridview 777 | #> 778 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidAssignmentToAutomaticVariable", "sender", Justification="Probably used by library code")] 779 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "e", Justification="Probably used by library code")] 780 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "sender", Justification="Probably used by library code")] 781 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Form", Justification="Reserved for future use")] 782 | [OutputType([system.Windows.Forms.DataGridView])] 783 | param( 784 | [Parameter(mandatory=$true)][System.Object]$Form, 785 | [Parameter(mandatory=$true)][System.Object]$Parent, 786 | [Parameter(mandatory=$true)][String]$Title, 787 | [Parameter(mandatory=$true)]$Data 788 | # [int]$Width = $GlobalWidth 789 | ) 790 | # Title label 791 | $Label = New-Object System.Windows.Forms.Label; 792 | $Label.Text = $Title; 793 | # $Label.MinimumSize = New-Object System.Drawing.Size($Width, 0); 794 | $Label.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right 795 | $Parent.Controls.Add($Label); 796 | # Create the DataTable 797 | $Table = New-Object system.Data.DataTable; 798 | $Col = New-Object system.Data.DataColumn "Selected", ([bool]); 799 | $Table.Columns.Add($Col); 800 | $Col = New-Object system.Data.DataColumn "Name", ([string]); 801 | $Table.Columns.Add($Col); 802 | $Col = New-Object system.Data.DataColumn "Description", ([string]); 803 | $Table.Columns.Add($Col); 804 | $Col = New-Object system.Data.DataColumn "Command", ([Object]); 805 | $Table.Columns.Add($Col); 806 | # Fill the DataTable 807 | foreach($element in $Data.GetEnumerator()) { 808 | $row = $Table.NewRow(); 809 | $row.Selected = $false; 810 | $row.Name = $element.Name; 811 | $row.Description = $element.Value[1]; 812 | $row.Command = $element.Value[0]; 813 | $Table.Rows.Add($row); 814 | } 815 | # Create the DataGridView 816 | $View = New-Object system.Windows.Forms.DataGridView; 817 | $View.DataSource = $Table; 818 | 819 | $View.AllowUserToAddRows = $false; 820 | $View.AllowUserToDeleteRows = $false; 821 | $View.AllowUserToOrderColumns = $false; 822 | $View.AllowUserToResizeColumns = $false; 823 | $View.AllowUserToResizeRows = $false; 824 | $View.AutoSizeColumnsMode = "AllCells"; 825 | $View.ShowEditingIcon = $false; 826 | 827 | # This enables the desired resizing behaviour, but does not work properly without AutoSizeMode or equivalent. 828 | # $View.AutoSize = $true; 829 | # This property does not exist for DataGridView. 830 | # $View.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink; 831 | 832 | # $View.Height = $Data.Count * 25 + 50; 833 | # $View.Width = $Width; 834 | 835 | $View.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right 836 | 837 | # https://forums.powershell.org/t/datagridview-hide-column/16739 838 | # https://stackoverflow.com/a/23763025/ 839 | $dataBindingComplete = { 840 | param ( 841 | [object]$sender, 842 | [System.EventArgs]$e 843 | ) 844 | Show-Output "Locking the UI from modifications and hiding unnecessary columns. (This does not work yet.)"; 845 | # Show-Output $View.Columns; 846 | foreach($column in $View.Columns) { 847 | if ($column.Name -ne "Selected") { 848 | $column.ReadOnly = $true; 849 | } 850 | } 851 | # $View.Columns["Command"].Visible = $false; 852 | } 853 | # $Form.Add_load($dataBindingComplete); 854 | 855 | $Parent.Controls.Add($View); 856 | # https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.datagridview.databindingcomplete 857 | $View.Add_DataBindingComplete($dataBindingComplete); 858 | 859 | return $View; 860 | } 861 | 862 | function Get-SelectedCommands { 863 | [OutputType([string[]])] 864 | param( 865 | [Parameter(mandatory=$true)][system.Windows.Forms.DataGridView]$View 866 | ) 867 | $rows = $View.DataSource.Select("Selected = 1") 868 | $commands = @() 869 | foreach($row in $rows) { 870 | $commands += $row.Command; 871 | } 872 | return $commands; 873 | } 874 | 875 | function Select-Cells { 876 | param( 877 | [Parameter(mandatory=$true)][system.Windows.Forms.DataGridView]$View, 878 | [Parameter(mandatory=$true)][System.Collections.Specialized.OrderedDictionary]$Dict, 879 | [Parameter(mandatory=$true)][string[]]$Names 880 | ) 881 | foreach ($Name in $Names) { 882 | $Index = @($Dict.Keys).IndexOf($Name) 883 | $View.rows[$Index].Cells[0].Value = $true 884 | } 885 | } 886 | 887 | ##### 888 | # Create the GUI 889 | ##### 890 | 891 | Install-Chocolatey 892 | Install-Winget 893 | 894 | # Import Windows Forms Assembly 895 | Add-Type -AssemblyName System.Windows.Forms; 896 | 897 | # Create the Form 898 | $Form = New-Object -TypeName System.Windows.Forms.Form; 899 | $AllDirectionsAnchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right 900 | $Form.Anchor = $AllDirectionsAnchor 901 | # $Form.AutoSize = $true; 902 | # $Form.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink; 903 | $Form.Text = "Mika's installer script" 904 | $Form.MinimumSize = New-Object System.Drawing.Size($GlobalWidth, $GlobalHeight); 905 | 906 | $Layout = New-Object System.Windows.Forms.TableLayoutPanel; 907 | $Layout.Anchor = $AllDirectionsAnchor 908 | # $Layout.AutoSize = $true; 909 | # $Layout.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink; 910 | # $Layout.BorderStyle = [System.Windows.Forms.BorderStyle]::Fixed3D; 911 | $Layout.RowCount = 6; 912 | $Form.Controls.Add($Layout); 913 | 914 | # Create grid views 915 | $ChocoProgramsView = New-Table -Form $Form -Parent $Layout -Title "Centrally updated programs (Chocolatey)" -Data $ChocoPrograms; 916 | $WingetProgramsView = New-Table -Form $Form -Parent $Layout -Title "Centrally updated programs (Winget)" -Data $WingetPrograms; 917 | $WingetProgramsView.Height = 50; 918 | $WindowsCapabilitiesView = New-Table -Form $Form -Parent $Layout -Title "Windows capabilities" -Data $WindowsCapabilities; 919 | $WindowsCapabilitiesView.Height = 150; 920 | $WindowsFeaturesView = New-Table -Form $Form -Parent $Layout -Title "Windows features" -Data $WindowsFeatures; 921 | $WindowsFeaturesView.Height = 95; 922 | $OtherOperationsView = New-Table -Form $Form -Parent $Layout -Title "Other programs and operations. These you have to keep updated manually." -Data $OtherOperations 923 | $OtherOperationsView.height = 150; 924 | 925 | # Disable unsupported features 926 | if (!(Test-CommandExists "choco")) { 927 | $ChocoProgramsView.Enabled = "$false"; 928 | } 929 | if (!(Test-CommandExists "winget")) { 930 | $WingetProgramsView.Enabled = $false; 931 | } 932 | if (!(Test-CommandExists "Add-WindowsCapability")) { 933 | $WindowsCapabilitiesView.Enabled = $false; 934 | } 935 | if (!(Test-CommandExists "Enable-WindowsOptionalFeature")) { 936 | $WindowsFeaturesView.Enabled = $false; 937 | } 938 | 939 | $ButtonsLayout = New-Object System.Windows.Forms.TableLayoutPanel; 940 | $ButtonsLayout.Anchor = $AllDirectionsAnchor 941 | $Layout.Controls.Add($ButtonsLayout) 942 | 943 | # Add OK button 944 | $Form | Add-Member -MemberType NoteProperty -Name Continue -Value $false; 945 | $OKButton = New-Object System.Windows.Forms.Button; 946 | $OKButton.Text = "OK"; 947 | $OKButton.Add_Click({ 948 | $Form.Continue = $true; 949 | $Form.Close(); 950 | }) 951 | $ButtonsLayout.Controls.Add($OKButton); 952 | 953 | # Add default buttons 954 | # These have to be after the grid view definitions. 955 | 956 | function Select-CommonDefaults { 957 | $CommonDefaultChocoPrograms = @("7-Zip", "Chocolatey GUI (RECOMMENDED!)", "Firefox", "VLC") 958 | if ((Get-CimInstance -Class Win32_Processor).Manufacturer -eq "GenuineIntel") { 959 | $CommonDefaultChocoPrograms += "Intel Driver & Support Assistant" 960 | } 961 | Select-Cells -View $ChocoProgramsView -Dict $ChocoPrograms -Names $CommonDefaultChocoPrograms 962 | Select-Cells -View $OtherOperationsView -Dict $OtherOperations -Names @("Maintenance") 963 | } 964 | 965 | function Select-LabDefaults { 966 | Select-CommonDefaults 967 | Select-Cells -View $ChocoProgramsView -Dict $ChocoPrograms -Names @("Notepad++") 968 | Select-Cells -View $WindowsCapabilitiesView -Dict $WindowsCapabilities -Names @("SNMP client") 969 | } 970 | 971 | function Select-PersonalDefaults { 972 | Select-CommonDefaults 973 | Select-Cells -View $ChocoProgramsView -Dict $ChocoPrograms -Names @("KeePassXC") 974 | } 975 | 976 | function Select-WorkstationDefaults { 977 | Select-CommonDefaults 978 | Select-Cells -View $ChocoProgramsView -Dict $ChocoPrograms -Names @("KeePassXC", "Notepad++", "OpenVPN", "PDF-XChange Editor", "Slack") 979 | Select-Cells -View $WindowsCapabilitiesView -Dict $WindowsCapabilities -Names @("SNMP client") 980 | Select-Cells -View $OtherOperationsView -Dict $OtherOperations -Names @("WithSecure Elements Agent") 981 | } 982 | 983 | if (Get-IsDomainJoined) { 984 | $WorkstationDefaultsButton = New-Object System.Windows.Forms.Button 985 | $WorkstationDefaultsButton.Text = "Select workstation defaults" 986 | $WorkstationDefaultsButton.Width = 160 987 | $WorkstationDefaultsButton.Add_Click({ 988 | Select-WorkstationDefaults 989 | }) 990 | $ButtonsLayout.Controls.Add($WorkstationDefaultsButton, 1, 0) 991 | 992 | $LabDefaultsButton = New-Object System.Windows.Forms.Button 993 | $LabDefaultsButton.Text = "Select lab defaults" 994 | $LabDefaultsButton.Width = 120 995 | $LabDefaultsButton.Add_Click({ 996 | Select-LabDefaults 997 | }) 998 | $ButtonsLayout.Controls.Add($LabDefaultsButton, 2, 0) 999 | } else { 1000 | $PersonalDefaultsButton = New-Object System.Windows.Forms.Button 1001 | $PersonalDefaultsButton.Text = "Select defaults for personal use" 1002 | $PersonalDefaultsButton.Width = 200 1003 | $PersonalDefaultsButton.Add_Click({ 1004 | Select-PersonalDefaults 1005 | }) 1006 | $ButtonsLayout.Controls.Add($PersonalDefaultsButton, 1, 0) 1007 | } 1008 | 1009 | function Resize-Layout { 1010 | $Layout.Width = $Form.Width - 10; 1011 | $Layout.Height = $Form.Height - 10; 1012 | $ChocoProgramsView.Height = $Form.Height - 660; 1013 | } 1014 | $Form.Add_Resize(${function:Resize-Layout}); 1015 | Resize-Layout; 1016 | 1017 | # Show the form 1018 | $Form.ShowDialog(); 1019 | if (! $Form.Continue) { 1020 | return 0; 1021 | } 1022 | 1023 | ##### 1024 | # Run the installations 1025 | ##### 1026 | 1027 | $ChocoSelected = Get-SelectedCommands $ChocoProgramsView 1028 | if ($ChocoSelected.Count) { 1029 | Show-Output "Installing programs with Chocolatey." 1030 | choco upgrade -y $ChocoSelected 1031 | } else { 1032 | Show-Output "No programs were selected to be installed with Chocolatey." 1033 | } 1034 | 1035 | $WingetSelected = Get-SelectedCommands $WingetProgramsView 1036 | if ($WingetSelected.Count) { 1037 | Show-Output "Installing programs with Winget. If asked to accept the license of the package repository, please select yes." 1038 | foreach($program in $WingetSelected) { 1039 | winget install "${program}" 1040 | } 1041 | } else { 1042 | Show-Output "No programs were selected to be installed with Winget." 1043 | } 1044 | 1045 | $WindowsCapabilitiesSelected = Get-SelectedCommands $WindowsCapabilitiesView 1046 | if ($WindowsCapabilitiesSelected.Count) { 1047 | Show-Output "Installing Windows capabilities." 1048 | foreach($capability in $WindowsCapabilitiesSelected) { 1049 | Show-Output "Installing ${capability}" 1050 | Add-WindowsCapability -Name "$capability" -Online 1051 | } 1052 | } else { 1053 | Show-Output "No Windows capabilities were selected to be installed." 1054 | } 1055 | 1056 | $WindowsFeaturesSelected = Get-SelectedCommands $WindowsFeaturesView 1057 | if ($WindowsFeaturesSelected.Count) { 1058 | Show-Output "Installing Windows features." 1059 | foreach($feature in $WindowsFeaturesSelected) { 1060 | Show-Output "Installing ${feature}" 1061 | Enable-WindowsOptionalFeature -FeatureName "$feature" -Online 1062 | } 1063 | } else { 1064 | Show-Output "No Windows features were selected to be installed." 1065 | } 1066 | 1067 | # These have to be after the package manager -based installations, as the package managers may install some Visual C++ runtimes etc., which we want to update automatically. 1068 | $OtherSelected = Get-SelectedCommands $OtherOperationsView 1069 | if ($OtherSelected.Count) { 1070 | Show-Output "Running other selected operations." 1071 | foreach($command in $OtherSelected) { 1072 | . $command 1073 | } 1074 | } else { 1075 | Show-Output "No other operations were selected." 1076 | } 1077 | 1078 | if (Test-RebootPending) { 1079 | Show-Output -ForegroundColor Cyan "The computer is pending a reboot. Please reboot the computer." 1080 | } 1081 | Show-Output -ForegroundColor Green "The installation script is ready. You can now close this window." 1082 | Stop-Transcript 1083 | --------------------------------------------------------------------------------