From d8acd6bac718b45a71851257d1eb06da597c1cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=20M=C3=A4ki?= Date: Fri, 3 Nov 2023 15:10:00 +0200 Subject: [PATCH] Add linting workflow and improve code quality --- .github/workflows/main.yml | 30 ++++++++++ .gitignore | 1 + Eject-Drive.ps1 | 4 +- Install-Software.ps1 | 17 ++++-- Maintenance.ps1 | 6 +- Profile/Microsoft.PowerShell_profile.ps1 | 6 +- Profile/Setup-Profile.ps1 | 2 +- Repair-Computer.ps1 | 2 +- Report.ps1 | 13 +++-- Reset-YubiKey.ps1 | 7 +-- Run-PerfTest.ps1 | 2 +- Setup-FIDO2-SSH.ps1 | 5 +- Utils.ps1 | 73 ++++++++++++++++++------ Zero-Free-Space.ps1 | 3 +- 14 files changed, 125 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..4f84fd0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,30 @@ +name: CI +on: push + +jobs: + lint: + name: Lint with PSScriptAnalyzer + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - name: Install PSScriptAnalyzer module + shell: pwsh + run: | + Set-PSRepository PSGallery -InstallationPolicy Trusted + Install-Module PSScriptAnalyzer -ErrorAction Stop + - name: Lint with PSScriptAnalyzer + shell: pwsh + run: | + Invoke-ScriptAnalyzer -Path *.ps1 -Recurse -Outvariable issues + $errors = $issues.Where({$_.Severity -eq 'Error'}) + $warnings = $issues.Where({$_.Severity -eq 'Warning'}) + if ($errors) { + Write-Error "There were $($errors.Count) errors and $($warnings.Count) warnings total." -ErrorAction Stop + } else { + Write-Output "There were $($errors.Count) errors and $($warnings.Count) warnings total." + } +# test-windows: +# name: Test on Windows +# runs-on: windows-latest +# steps: diff --git a/.gitignore b/.gitignore index 37d5bca..7e2b245 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ obs-studio/profiler_data obs-studio/updates reports reports.zip +venv diff --git a/Eject-Drive.ps1 b/Eject-Drive.ps1 index 461cd05..9bc2407 100644 --- a/Eject-Drive.ps1 +++ b/Eject-Drive.ps1 @@ -8,6 +8,7 @@ Drive letter #> +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "DriveLetter", Justification="Set to null on purpose")] param( [Parameter(Mandatory=$true)][char]$DriveLetter ) @@ -16,7 +17,8 @@ param( # $driveEject = New-Object -comObject Shell.Application # $driveEject.Namespace(17).ParseName("${DriveLetter}:").InvokeVerb("Eject") -$vol = Get-WmiObject -Class Win32_Volume | where{$_.Name -eq "${DriveLetter}:\"} +# Todo: replace WMI with CIM +$vol = Get-WmiObject -Class Win32_Volume | Where-Object {$_.Name -eq "${DriveLetter}:\"} $vol.DriveLetter = $null $vol.Put() $vol.Dismount($false, $false) diff --git a/Install-Software.ps1 b/Install-Software.ps1 index 0b8b094..dce663e 100644 --- a/Install-Software.ps1 +++ b/Install-Software.ps1 @@ -5,11 +5,12 @@ This parameter is for internal use to check whether an UAC prompt has already been attempted. #> +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Elevated", Justification="Used in utils")] param( [switch]$Elevated ) -. ".\Utils.ps1" +. "${PSScriptRoot}\Utils.ps1" if ($RepoInUserDir) { Update-Repo @@ -282,7 +283,7 @@ function Install-OriginViewer { Show-Output "Extracting Origin Viewer" Expand-Archive -Path "${Downloads}\$Filename" -DestinationPath "${DestinationPath}" $ExePath = Find-First -Filter "*.exe" -Path "${DestinationPath}" - if ($ExePath -eq $null) { + if ($null -eq $ExePath) { Show-Information "No exe file was found in the extracted directory." -ForegroundColor Red return $false } @@ -392,13 +393,18 @@ function Install-ThorlabsBeam ([string]$Version = "8.2.5232.395") { } } -function Install-ThorlabsKinesis ([string]$Version = "1.14.36", [string]$Version2 = "20973") { +function Install-ThorlabsKinesis () { <# .SYNOPSIS Install Thorlabs Kinesis .LINK https://www.thorlabs.com/software_pages/ViewSoftwarePage.cfm?Code=Motion_Control&viewtab=0 #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Thorlabs Kinesis is a singular noun")] + param( + [string]$Version = "1.14.36", + [string]$Version2 = "20973" + ) Show-Output "Downloading Thorlabs Kinesis. The web server has strict bot detection and the download may therefore fail, producing an invalid file." $Arch = Get-InstallBitness -x86 "x86" -x86_64 "x64" $Filename = "kinesis_${Version2}_setup_${Arch}.exe" @@ -509,6 +515,9 @@ function CreateTable { .LINK https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.datagridview #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "e", Justification="Probably used by library code")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "sender", Justification="Probably used by library code")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Form", Justification="Reserved for future use")] [OutputType([system.Windows.Forms.DataGridView])] param( [Parameter(mandatory=$true)][System.Object]$Form, @@ -707,7 +716,7 @@ if ($WindowsFeaturesSelected.Count) { Show-Output "Installing Windows features." foreach($feature in $WindowsFeaturesSelected) { Show-Output "Installing ${feature}" - Enable-WindowsOptionalFeature -Feature "$feature" -Online + Enable-WindowsOptionalFeature -FeatureName "$feature" -Online } } else { Show-Output "No Windows features were selected to be installed." diff --git a/Maintenance.ps1 b/Maintenance.ps1 index 9ac37c4..ab15632 100644 --- a/Maintenance.ps1 +++ b/Maintenance.ps1 @@ -34,8 +34,7 @@ param( ) # Load utility functions from another file. -Set-Location "${PSScriptRoot}" -. ".\Utils.ps1" +. "${PSScriptRoot}\Utils.ps1" if ($Reboot -and $Shutdown) { # The Show-Output function is defined in Utils.ps1 @@ -601,7 +600,10 @@ if ($Zerofree) { .\zero-free-space.ps1 -DriveLetter "C" } +Show-Output -ForegroundColor Cyan "Writing maintenance timestamp." Get-Date -Format "o" | Out-File $TimestampPath +$ThisRunDate = [Datetime](Get-Content $TimestampPath) +Show-Output -ForegroundColor Cyan "Saved timestamp: ${ThisRunDate}" Show-Output -ForegroundColor Green "The maintenance script is ready." if ($Reboot) { diff --git a/Profile/Microsoft.PowerShell_profile.ps1 b/Profile/Microsoft.PowerShell_profile.ps1 index e77e40f..4785ea4 100644 --- a/Profile/Microsoft.PowerShell_profile.ps1 +++ b/Profile/Microsoft.PowerShell_profile.ps1 @@ -7,7 +7,7 @@ function Enable-SSHAgent { # Be aware that if you are missing these lines from your profile, tab completion # for `choco` will not function. # See https://ch0.co/tab-completion for details. -$ChocolateyProfile = "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" -if (Test-Path($ChocolateyProfile)) { - Import-Module "$ChocolateyProfile" +$ChocolateyProfile = "${env:ChocolateyInstall}\helpers\chocolateyProfile.psm1" +if (Test-Path("${ChocolateyProfile}")) { + Import-Module "${ChocolateyProfile}" } diff --git a/Profile/Setup-Profile.ps1 b/Profile/Setup-Profile.ps1 index 637a2a8..dbd3889 100644 --- a/Profile/Setup-Profile.ps1 +++ b/Profile/Setup-Profile.ps1 @@ -1,7 +1,7 @@ Set-StrictMode -Latest $ErrorActionPreference = "Stop" -. "$((Get-Item ${PSScriptRoot}).Parent.FullName)/utils.ps1" +. "$(Join-Path $((Get-Item "${PSScriptRoot}").Parent.FullName) "Utils.ps1")" Show-Output "Configuring PowerShell profile." diff --git a/Repair-Computer.ps1 b/Repair-Computer.ps1 index b7b8a08..6ddc9a8 100644 --- a/Repair-Computer.ps1 +++ b/Repair-Computer.ps1 @@ -3,7 +3,7 @@ Fix various issues with Windows #> -. "./Utils.ps1" +. "${PSScriptRoot}\Utils.ps1" Elevate($MyInvocation.MyCommand.Definition) Start-Transcript -Path "${LogPath}\Repair-Computer_$(Get-Date -Format "yyyy-MM-dd_HH-mm").txt" diff --git a/Report.ps1 b/Report.ps1 index ddc2846..e12bff2 100644 --- a/Report.ps1 +++ b/Report.ps1 @@ -8,21 +8,22 @@ #> param( + [switch]$Elevated, [switch]$NoArchive ) -. ".\Utils.ps1" +. "${PSScriptRoot}\Utils.ps1" Elevate($myinvocation.MyCommand.Definition) $host.ui.RawUI.WindowTitle = "Mika's reporting script" -$Downloads = ".\downloads" -$Reports = ".\reports" +# $Downloads = ".\Downloads" +$Reports = ".\Reports" function Compress-ReportArchive { Show-Output "Creating the report archive" $Timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm" - Compress-Archive -Path "${Reports}" -DestinationPath ".\reports_${Timestamp}.zip" -CompressionLevel Optimal + Compress-Archive -Path "${Reports}" -DestinationPath ".\Reports_${Timestamp}.zip" -CompressionLevel Optimal } if ($OnlyArchive) { @@ -34,7 +35,7 @@ if ($OnlyArchive) { # Initialization # ----- Show-Output "Running Mika's reporting script" -New-Item -Path "." -Name "reports" -ItemType "directory" -Force | Out-Null +New-Item -Path "." -Name "Reports" -ItemType "directory" -Force | Out-Null Show-Output "Removing old reports" Get-ChildItem "${Reports}/*" -Recurse | Remove-Item @@ -87,7 +88,7 @@ if (Test-CommandExists "gpresult") { if (Test-CommandExists "netsh") { Show-Output "Creating WiFi report" netsh wlan show wlanreport - Copy-Item "C:\ProgramData\Microsoft\Windows\WlanReport\wlan-report-latest.html" "${Reports}" + Copy-Item "C:\ProgramData\Microsoft\Windows\WlanReport\wlan_report_latest.html" "${Reports}" } if (Test-CommandExists "powercfg") { diff --git a/Reset-YubiKey.ps1 b/Reset-YubiKey.ps1 index 426d19f..95a819c 100644 --- a/Reset-YubiKey.ps1 +++ b/Reset-YubiKey.ps1 @@ -5,15 +5,14 @@ This parameter is for internal use to check whether an UAC prompt has already been attempted. #> +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Elevated", Justification="Used in utils")] param( [switch]$Elevated ) -. ".\Utils.ps1" - +. "${PSScriptRoot}\Utils.ps1" Elevate($myinvocation.MyCommand.Definition) - -. ".\venv\Scripts\activate.ps1" +. "${PSScriptRoot}\venv\Scripts\activate.ps1" Show-Output -ForegroundColor Cyan "Resetting FIDO" ykman fido reset diff --git a/Run-PerfTest.ps1 b/Run-PerfTest.ps1 index 7a3ff08..53af57a 100644 --- a/Run-PerfTest.ps1 +++ b/Run-PerfTest.ps1 @@ -19,7 +19,7 @@ param( [switch]$Unigine ) -. "./Utils.ps1" +. "${PSScriptRoot}\Utils.ps1" Elevate($MyInvocation.MyCommand.Definition) Start-Transcript -Path "${LogPath}\perftest_$(Get-Date -Format "yyyy-MM-dd_HH-mm").txt" diff --git a/Setup-FIDO2-SSH.ps1 b/Setup-FIDO2-SSH.ps1 index 54dc5c8..87cd897 100644 --- a/Setup-FIDO2-SSH.ps1 +++ b/Setup-FIDO2-SSH.ps1 @@ -5,14 +5,13 @@ This parameter is for internal use to check whether an UAC prompt has already been attempted. #> +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Elevated", Justification="Used in utils")] param( [switch]$Elevated ) -. ".\Utils.ps1" - +. "${PSScriptRoot}\Utils.ps1" Elevate($myinvocation.MyCommand.Definition) - . ".\venv\Scripts\activate.ps1" Show-Output -ForegroundColor Cyan "Changing the FIDO2 PIN" diff --git a/Utils.ps1 b/Utils.ps1 index 12a07cb..647635a 100644 --- a/Utils.ps1 +++ b/Utils.ps1 @@ -3,13 +3,17 @@ Utility methods for scripts #> +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidAssignmentToAutomaticVariable", "", Justification="Overriding only for old PowerShell versions")] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "LogPath", Justification="Used in scripts")] +param() + # Compatibility for old PowerShell versions if($PSVersionTable.PSVersion.Major -lt 3) { # Configure PSScriptRoot variable for old PowerShell versions # https://stackoverflow.com/a/8406946/ # https://stackoverflow.com/a/5466355/ $invocation = (Get-Variable MyInvocation).Value - $PSScriptRoot = $directorypath = Split-Path $invocation.MyCommand.Path + $PSScriptRoot = Split-Path $invocation.MyCommand.Path # Implement missing features @@ -20,6 +24,7 @@ if($PSVersionTable.PSVersion.Major -lt 3) { .LINK https://stackoverflow.com/a/43544903 #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidOverwritingBuiltInCmdlets", "", Justification="Overriding only for old PowerShell versions")] param( [Parameter(Mandatory=$true)][string]$Uri, [Parameter(Mandatory=$true)][string]$OutFile @@ -38,6 +43,7 @@ if($PSVersionTable.PSVersion.Major -lt 3) { .LINK https://stackoverflow.com/a/54687028 #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidOverwritingBuiltInCmdlets", "", Justification="Overriding only for old PowerShell versions")] param( [Parameter(Mandatory=$true)][string]$Path, [Parameter(Mandatory=$true)][string]$DestinationPath @@ -54,10 +60,10 @@ if($PSVersionTable.PSVersion.Major -lt 3) { $RepoPath = $PSScriptRoot # Create sub-directories of the repo so that the scripts don't have to create them. -New-Item -Path "$RepoPath" -Name "downloads" -ItemType "directory" -Force | Out-Null -New-Item -Path "$RepoPath" -Name "logs" -ItemType "directory" -Force | Out-Null -$Downloads = "${RepoPath}\downloads" -$LogPath = "${RepoPath}\logs" +New-Item -Path "$RepoPath" -Name "Downloads" -ItemType "directory" -Force | Out-Null +New-Item -Path "$RepoPath" -Name "Logs" -ItemType "directory" -Force | Out-Null +$Downloads = "${RepoPath}\Downloads" +$LogPath = "${RepoPath}\Logs" # Define some useful paths $CommonDesktopPath = [Environment]::GetFolderPath("CommonDesktopDirectory") @@ -67,12 +73,19 @@ $StartPath = Get-Location # Define some useful variables $RepoInUserDir = ([System.IO.DirectoryInfo]"${RepoPath}").FullName.StartsWith(([System.IO.DirectoryInfo]"${env:UserProfile}").FullName) -# Force Invoke-WebRequest to use TLS 1.2 instead of the insecure TLS 1.0, which is the default. +# Force Invoke-WebRequest to use TLS 1.2 and TLS 1.3 instead of the insecure TLS 1.0, which is the default. # https://stackoverflow.com/a/41618979 -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +if ([Net.SecurityProtocolType]::Tls13) { + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 +} else { + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +} function Add-ScriptShortcuts { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Plural on purpose")] + param() + if ($RepoInUserDir) { $BasePath = $DesktopPath } else { @@ -91,7 +104,7 @@ function Add-ScriptShortcuts { New-Shortcut -Path $InstallShortcutPath -TargetPath "powershell" -Arguments "-File ${RepoPath}\Install-Software.ps1" -WorkingDirectory "${RepoPath}" -IconLocation "shell32.dll,21" | Out-Null # } # if(!(Test-Path $MaintenanceShortcutPath)) { - New-Shortcut -Path $MaintenanceShortcutPath -TargetPath "powershell" -Arguments "-File ${RepoPath}\maintenance.ps1" -WorkingDirectory "${RepoPath}" -IconLocation "shell32.dll,80" | Out-Null + New-Shortcut -Path $MaintenanceShortcutPath -TargetPath "powershell" -Arguments "-File ${RepoPath}\Maintenance.ps1" -WorkingDirectory "${RepoPath}" -IconLocation "shell32.dll,80" | Out-Null # } New-Shortcut -Path $RepoShortcutPath -TargetPath "${RepoPath}" | Out-Null @@ -176,15 +189,15 @@ function Get-InstallBitness { param( [string]$x86 = "x86", [string]$x86_64 = "x86_64", - [switch]$Verbose = $true + [switch]$Quiet = $false ) if ([System.Environment]::Is64BitOperatingSystem) { - if ($Verbose) { + if (-not $Quiet) { Show-Information "64-bit operating system detected. Installing 64-bit version." } return $x86_64 } - if ($Verbose) { + if (-not $Quiet) { Show-Information "32-bit operating system detected. Installing 32-bit version." } return $x86 @@ -254,6 +267,7 @@ function GitPull { } function Install-Chocolatey { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingInvokeExpression", "", Justification="Chocolatey installation requires Invoke-Expression")] param( [switch]$Force ) @@ -262,7 +276,7 @@ function Install-Chocolatey { Set-ExecutionPolicy Bypass -Scope Process -Force # This is already configured globally above # [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 - iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) + Invoke-Expression ((New-Object System.Net.WebClient).DownloadString("https://chocolatey.org/install.ps1")) } } @@ -390,6 +404,7 @@ function New-Shortcut { .LINK https://stackoverflow.com/a/9701907 #> + [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory=$true)][string]$Path, [Parameter(Mandatory=$true)][string]$TargetPath, @@ -414,7 +429,9 @@ function New-Shortcut { if (! [string]::IsNullOrEmpty($IconLocation)) { $Shortcut.IconLocation = $IconLocation } - $Shortcut.Save() + if($PSCmdlet.ShouldProcess($Path, "Save")) { + $Shortcut.Save() + } return $Shortcut } @@ -428,6 +445,10 @@ function Request-DomainConnection { } function Set-RepoPermissions { + [CmdletBinding(SupportsShouldProcess)] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Plural on purpose")] + param() + Import-Module Microsoft.PowerShell.Security if ($RepoInUserDir) { Show-Output "The repo is installed within the user folder. Ensuring that it's owned by the user." @@ -439,17 +460,23 @@ function Set-RepoPermissions { if ($CurrentOwner -ne $User) { Show-Output "The repo is owned by `"${CurrentOwner}`". Changing it to: `"${User}`"" $ACL.SetOwner($User) - $ACL | Set-Acl -Path "${RepoPath}" + if($PSCmdlet.ShouldProcess($RepoPath, "Set-Acl")) { + $ACL | Set-Acl -Path "${RepoPath}" + } } } else { Show-Output "The repo is installed globally. Ensuring that it's protected." $ACL = Get-Acl -Path "${env:ProgramFiles}" - $ACL | Set-Acl -Path "${RepoPath}" + if($PSCmdlet.ShouldProcess($RepoPath, "Set-Acl")) { + $ACL | Set-Acl -Path "${RepoPath}" + } $RepoPathObj = (Get-Item $RepoPath) if (($RepoPathObj.Parent.FullName -eq "Git") -and ($RepoPathObj.Parent.Parent.FullName -eq $env:SystemDrive)) { Show-Output "The repo is located in the Git folder at the root of the system drive. Protecting the Git folder." $ACL = Get-Acl -Path $env:SystemDrive - $ACL | Set-Acl -Path $RepoPathObj.Parent + if($PSCmdlet.ShouldProcess($RepoPathObj, "Set-Acl")) { + $ACL | Set-Acl -Path $RepoPathObj.Parent + } } } } @@ -542,6 +569,7 @@ function Test-Admin { } Function Test-CommandExists { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Not plural")] [OutputType([bool])] Param( [Parameter(Mandatory=$true)][string]$command @@ -610,6 +638,7 @@ function Update-Repo { .SYNOPSIS Update the repository if enough time has passed since the previous "git pull" #> + [CmdletBinding(SupportsShouldProcess)] [OutputType([bool])] param( [TimeSpan]$MaxTimeSpan = (New-TimeSpan -Days 1) @@ -621,7 +650,9 @@ function Update-Repo { $FetchHeadPath = "${RepoPath}\.git\FETCH_HEAD" if (!(Test-Path "${FetchHeadPath}")) { Show-Output "The date of the previous `"git pull`" could not be determined. Updating." - git pull + if($PSCmdlet.ShouldProcess($RepoPath, "git pull")) { + git pull + } Show-Output "You may have to restart the script to use the new version." # return $true return @@ -638,10 +669,14 @@ function Update-Repo { $RepoName = Split-Path "${RepoPath}" -Leaf if ((! (Test-Path "${GitConfigPath}")) -or (! (Select-String "${GitConfigPath}" -Pattern "${RepoName}" -SimpleMatch))) { Show-Output "Git config was not found, or it did not contain the repository path. Adding ${RepoPath} as a safe directory." - git config --global --add safe.directory "${RepoPath}" + if($PSCmdlet.ShouldProcess($RepoPath, "git config")) { + git config --global --add safe.directory "${RepoPath}" + } } } - git pull + if($PSCmdlet.ShouldProcess($RepoPath, "git pull")) { + git pull + } Show-Output "You may have to restart the script to use the new version." # return $true } diff --git a/Zero-Free-Space.ps1 b/Zero-Free-Space.ps1 index c171a55..24d5064 100644 --- a/Zero-Free-Space.ps1 +++ b/Zero-Free-Space.ps1 @@ -9,12 +9,13 @@ Name of the drive to be cleaned #> +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "Elevated", Justification="Used in utils")] param( [Parameter(Mandatory=$true)] [string]$DriveLetter, [switch]$Elevated ) -. "./Utils.ps1" +. "${PSScriptRoot}\Utils.ps1" Elevate($myinvocation.MyCommand.Definition) if (! (Test-Path "./lib/SDelete")) {