Function Test-CommandExists { <# .SYNOPSIS Returns true if a command is present in PowerShell .DESCRIPTION A longer description. .PARAMETER FirstParameter Description of each of the parameters. Note: To make it easier to keep the comments synchronized with changes to the parameters, the preferred location for parameter documentation comments is not here, but within the param block, directly above each parameter. .PARAMETER SecondParameter Description of each of the parameters. .INPUTS Description of objects that can be piped to the script. .OUTPUTS Description of objects that are output by the script. .EXAMPLE Example of how to run the script. .LINK https://devblogs.microsoft.com/scripting/use-a-powershell-function-to-see-if-a-command-exists/ .NOTES Detail on what the script does, if this is needed. #> Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Command ) Try { If (Get-Command $Command -ErrorAction Stop) { Return $true } } Catch { Return $false } } Function Add-LogEntry { <# .SYNOPSIS A brief description of the function or script. .DESCRIPTION A longer description. .PARAMETER FirstParameter Description of each of the parameters. Note: To make it easier to keep the comments synchronized with changes to the parameters, the preferred location for parameter documentation comments is not here, but within the param block, directly above each parameter. .PARAMETER SecondParameter Description of each of the parameters. .INPUTS Description of objects that can be piped to the script. .OUTPUTS Description of objects that are output by the script. .EXAMPLE Example of how to run the script. .LINK Links to further documentation. .NOTES Detail on what the script does, if this is needed. #> Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Message, [Parameter(Mandatory = $false)] [ValidateSet('Notify', 'Warning', 'Error', 'Debug')] [String] $As, [Parameter(Mandatory = $false)] [Switch] $NewLine ) Switch ($As) { "Notify" { $FGC = "Cyan" } "Warning" { $FGC = "Yellow" } "Error" { $FGC = "Red" } "Debug" { $FGC = "Magenta" } Default { $FGC = "White" } } If ($Newline) { $Output += "`n" } $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $Output += "[$Timestamp] $Message" If ($As -eq "Debug") { If ($Dev) { Write-Host $Output -ForegroundColor $FGC } } Else { Write-Host $Output -ForegroundColor $FGC } } Function Add-ListItem () { <# .SYNOPSIS A brief description of the function or script. .DESCRIPTION A longer description. .PARAMETER FirstParameter Description of each of the parameters. Note: To make it easier to keep the comments synchronized with changes to the parameters, the preferred location for parameter documentation comments is not here, but within the param block, directly above each parameter. .PARAMETER SecondParameter Description of each of the parameters. .INPUTS Description of objects that can be piped to the script. .OUTPUTS Description of objects that are output by the script. .EXAMPLE Example of how to run the script. .LINK Links to further documentation. .NOTES Detail on what the script does, if this is needed. #> Param ( [Parameter(Mandatory = $true)] [ValidateNotNullorEmpty()] [String] $Message, [Parameter(Mandatory = $false)] [Int16] $Indent = 0 ) For ($Counter = 0; $Counter -le $Indent; $Counter++) { $Output += " " } $Output += "* $Message" Write-Host $Output } Function Initialize-PSGallery { <# .SYNOPSIS A brief description of the function or script. .DESCRIPTION A longer description. .PARAMETER FirstParameter Description of each of the parameters. Note: To make it easier to keep the comments synchronized with changes to the parameters, the preferred location for parameter documentation comments is not here, but within the param block, directly above each parameter. .PARAMETER SecondParameter Description of each of the parameters. .INPUTS Description of objects that can be piped to the script. .OUTPUTS Description of objects that are output by the script. .EXAMPLE Example of how to run the script. .LINK Links to further documentation. .NOTES Detail on what the script does, if this is needed. #> Add-LogEntry "Preparing system to download modules" If ([Net.ServicePointManager]::SecurityProtocol -ne [Net.SecurityProtocolType]::SystemDefault) { Add-LogEntry "Upgrading TLS security protocol to 1.2" Try { [Net.ServicePointManager]::SecurityProtocol = @([Net.SecurityProtocolType]::Tls, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls12) } Catch { Add-LogEntry "Upgrade failed, script is exiting" -As Error Exit 1 } } If ((Get-PackageProvider).Name -notcontains "NuGet") { Add-LogEntry "Installing NuGet package provider" Try { Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ErrorAction Stop } Catch { Add-LogEntry "Installation failed, script is exiting" -As Error Exit 1 } } $PSRepos = Get-PSRepository If ($PSRepos.Name -notcontains "PSGallery") { Add-LogEntry "Registering PSGallery as script source" Try { Register-PSRepository -Default -InstallationPolicy Trusted -ErrorAction Stop } Catch { Add-LogEntry "Change failed, script is exiting" -As Error Exit 1 } } ElseIf ($PSRepos | Where-Object { $_.Name -eq "PSGallery" -and $_.InstallationPolicy -ne "Trusted" }) { Add-LogEntry "Trusting scripts from PSGallery" Try { Set-PSRepository PSGallery -InstallationPolicy Trusted -ErrorAction Stop } Catch { Add-LogEntry "Change failed, script is exiting" -As Error Exit 1 } } } Function Import-Modules { <# .SYNOPSIS A brief description of the function or script. .DESCRIPTION A longer description. .PARAMETER FirstParameter Description of each of the parameters. Note: To make it easier to keep the comments synchronized with changes to the parameters, the preferred location for parameter documentation comments is not here, but within the param block, directly above each parameter. .PARAMETER SecondParameter Description of each of the parameters. .INPUTS Description of objects that can be piped to the script. .OUTPUTS Description of objects that are output by the script. .EXAMPLE Example of how to run the script. .LINK Links to further documentation. .NOTES Detail on what the script does, if this is needed. #> Param ( [Parameter(Mandatory=$true)] $ModuleList ) If ($ModuleList) { Initialize-PSGallery ForEach ($Module in $ModuleList) { If (-Not (Get-Module -ListAvailable -Name $Module)) { Try { Add-LogEntry "Module `"$Module`" not found, installing" Install-Module -Name $Module -Scope CurrentUser -Force -AllowClobber } Catch { Add-LogEntry "Module failed to install automatically! Manaully install the module, then re-run the script." -As Error Exit 1 } } Import-Module $Module } } } Function Import-JSONConfig () { <# .SYNOPSIS A brief description of the function or script. .DESCRIPTION A longer description. .PARAMETER FirstParameter Description of each of the parameters. Note: To make it easier to keep the comments synchronized with changes to the parameters, the preferred location for parameter documentation comments is not here, but within the param block, directly above each parameter. .PARAMETER SecondParameter Description of each of the parameters. .INPUTS Description of objects that can be piped to the script. .OUTPUTS Description of objects that are output by the script. .EXAMPLE Example of how to run the script. .LINK Links to further documentation. .NOTES Detail on what the script does, if this is needed. #> Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $ConfigFile ) If (Test-Path -Path $ConfigFile) { Try { Return (Get-Content -Raw -Path $ConfigFile | ConvertFrom-Json) } Catch { Add-LogEntry "Error loading config file! Please make sure the correct path was provided, and that it is a properly formatted JSON file" -As Error Write-Output $_ Exit 1 } } Else { Add-LogEntry "Error loading config file! Please make sure the correct path was provided, and that it is a properly formatted JSON file" -As Error Write-Output $_ Exit 1 } } Function Import-YAMLConfig () { <# .SYNOPSIS A brief description of the function or script. .DESCRIPTION A longer description. .PARAMETER FirstParameter Description of each of the parameters. Note: To make it easier to keep the comments synchronized with changes to the parameters, the preferred location for parameter documentation comments is not here, but within the param block, directly above each parameter. .PARAMETER SecondParameter Description of each of the parameters. .INPUTS Description of objects that can be piped to the script. .OUTPUTS Description of objects that are output by the script. .EXAMPLE Example of how to run the script. .LINK Links to further documentation. .NOTES Detail on what the script does, if this is needed. #> Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $ConfigFile ) If ( -Not (Get-Module -ListAvailable -Name "powershell-yaml")) { Add-LogEntry "Module `"powershell-yaml`" not present, but needed for YAML imports. Adding..." Try { Initialize-PSGallery Install-Module -Name "powershell-yaml" -Scope CurrentUser -Force -AllowClober } Catch { Add-LogEntry "Module failed to install automatically! Manaully install the module, then re-run the script." -As Error Exit 1 } } If (Test-Path -Path $ConfigFile) { Try { Return (Get-Content -Raw -Path $ConfigFile | ConvertFrom-Yaml) } Catch { Add-LogEntry "Error loading config file! Please make sure the correct path was provided, and that it is a properly formatted JSON file" -As Error Write-Output $_ Exit 1 } } Else { Add-LogEntry "Error loading config file! Please make sure the correct path was provided, and that it is a properly formatted JSON file" -As Error Write-Output $_ Exit 1 } } Function Import-CSVConfig () { <# .SYNOPSIS A brief description of the function or script. .DESCRIPTION A longer description. .PARAMETER FirstParameter Description of each of the parameters. Note: To make it easier to keep the comments synchronized with changes to the parameters, the preferred location for parameter documentation comments is not here, but within the param block, directly above each parameter. .PARAMETER SecondParameter Description of each of the parameters. .INPUTS Description of objects that can be piped to the script. .OUTPUTS Description of objects that are output by the script. .EXAMPLE Example of how to run the script. .LINK Links to further documentation. .NOTES Detail on what the script does, if this is needed. #> Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] $ConfigFile ) If (Test-Path -Path $ConfigFile) { Try { Return (Import-Csv $ConfigFile) } Catch { Add-LogEntry "Error loading config file! Please make sure the correct path was provided, and that it is a properly formatted CSV file" -As Error Write-Host $_ Exit } } Else { Add-LogEntry "Error loading config file! Please make sure the correct path was provided, and that it is a properly formatted CSV file" -As Error Write-Host $_ Exit } } Function Import-Config () { <# .SYNOPSIS A brief description of the function or script. .DESCRIPTION A longer description. .PARAMETER FirstParameter Description of each of the parameters. Note: To make it easier to keep the comments synchronized with changes to the parameters, the preferred location for parameter documentation comments is not here, but within the param block, directly above each parameter. .PARAMETER SecondParameter Description of each of the parameters. .INPUTS Description of objects that can be piped to the script. .OUTPUTS Description of objects that are output by the script. .EXAMPLE Example of how to run the script. .LINK Links to further documentation. .NOTES Detail on what the script does, if this is needed. #> Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] $ConfigFile ) If (Test-Path -Path $ConfigFile) { Try { Switch ((Get-Item -Path $ConfigFile).Extension.ToLower()) { { @( ".yml", ".yaml").Contains($_) } { Return (Import-YAMLConfig $ConfigFile) } { @( ".jsn", ".json").Contains($_) } { Return (Import-JSONConfig $ConfigFile) } { @( ".csv").Contains($_) } { Return (Import-CSVConfig $ConfigFile) } } } Catch { Add-LogEntry "Error loading config file! Please make sure the correct path was provided, and that it is a properly formatted CSV file" -As Error Write-Host $_ Exit } } Else { Add-LogEntry "Error loading config file! Please make sure the correct path was provided, and that it is a properly formatted file" -As Error Write-Host $_ Exit } } Function Set-RegistryKey { Param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] $TestPath, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] $TestKey, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] $TestValue, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] $TestType ) If (-Not (Test-Path -Path $TestPath)) { Add-LogEntry "Registry path not found, creating..." New-Item $TestPath -Force -ErrorAction SilentlyContinue | Out-Null } If ($null -ne (Get-Item -Path $TestPath).GetValue($TestKey)) { If ((Get-ItemPropertyValue -Path $TestPath -Name $TestKey) -eq $TestValue) { Add-LogEntry "Current key value is `"$((Get-Item -Path $TestPath).GetValue($TestKey))`", no action needed" } Else { Add-LogEntry "Setting value of `"$($TestKey)`" to `"$($TestValue)`"" Set-ItemProperty -Path $TestPath -Name $TestKey -Value $TestValue } } Else { Add-LogEntry "Registry key does not exist, creating..." New-ItemProperty -Path $TestPath -Name $TestKey -Value $TestValue -PropertyType $TestType -Force -ErrorAction SilentlyContinue | Out-Null } } Function Save-Parameters { Param ([hashtable]$NamedParameters) Return ($NamedParameters.GetEnumerator() | ForEach-Object { "-$($_.Key) `"$($_.Value)`"" }) -join " " } Function Test-AsAdmin () { <# .SYNOPSIS A brief description of the function or script. .DESCRIPTION A longer description. .PARAMETER FirstParameter Description of each of the parameters. Note: To make it easier to keep the comments synchronized with changes to the parameters, the preferred location for parameter documentation comments is not here, but within the param block, directly above each parameter. .PARAMETER SecondParameter Description of each of the parameters. .INPUTS Description of objects that can be piped to the script. .OUTPUTS Description of objects that are output by the script. .EXAMPLE Example of how to run the script. .LINK Links to further documentation. .NOTES Detail on what the script does, if this is needed. #> Param ( [Parameter(Mandatory = $false)] [String] $AdditionalFlags ) Add-LogEntry "Checking to see if script has been run with administrative privileges" if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) { Add-LogEntry "Performing script elevation, please approve the admin prompt" Start-Sleep -Seconds 3 if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) { $CommandLine = "-ExecutionPolicy Bypass -File `"" + $MyInvocation.MyCommand.Path + "`" " + (Save-Parameters $MyInvocation.BoundParameters) + " " + $MyInvocation.UnboundArguments Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine Exit } } Else { Add-LogEntry "Script already running as admin" } } # Origin: https://lucyllewy.com/powershell-clickable-hyperlinks/ Function Format-Hyperlink { Param( [Parameter(ValueFromPipeline = $true, Position = 0)][ValidateNotNullOrEmpty()] [Uri] $Uri, [Parameter(Mandatory = $false, Position = 1)] [String] $Label ) If (($PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows) -and -not $Env:WT_SESSION) { # Fallback for Windows users not inside Windows Terminal If ($Label) { Return "$Label ($Uri)" } Return "$Uri" } If ($Label) { Return "`e]8;;$Uri`e\$Label`e]8;;`e\" } Return "$Uri" }