Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix WinUtil admin elevation for stable and pre-release builds #2908

Closed
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 64 additions & 28 deletions scripts/start.ps1
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<#
.NOTES
Author : Chris Titus @christitustech
Runspace Author: @DeveloperDurp
GitHub : https://github.com/ChrisTitusTech
Version : #{replaceme}
Author : Chris Titus @christitustech
Runspace Author : @DeveloperDurp
GitHub : https://github.com/ChrisTitusTech
Version : #{replaceme}
#>

# Define the arguments for the WinUtil script
param (
[switch]$Debug,
[string]$Config,
Expand All @@ -17,12 +18,13 @@ if ($Debug) {
$DebugPreference = "Continue"
}

# Handle the -Config parameter
if ($Config) {
$PARAM_CONFIG = $Config
}

$PARAM_RUN = $false
# Handle the -Run switch
$PARAM_RUN = $false
if ($Run) {
Write-Host "Running config file tasks..."
$PARAM_RUN = $true
Expand All @@ -39,38 +41,72 @@ $sync.version = "#{replaceme}"
$sync.configs = @{}
$sync.ProcessRunning = $false

if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "Winutil needs to be run as Administrator. Attempting to relaunch."
$argList = @()
# Store elevation status of the process
$isElevated = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

$PSBoundParameters.GetEnumerator() | ForEach-Object {
$argList += if ($_.Value -is [switch] -and $_.Value) {
"-$($_.Key)"
} elseif ($_.Value) {
"-$($_.Key) `"$($_.Value)`""
}
}
# Initialize the arguments list array
$argsList = @()

$script = if ($MyInvocation.MyCommand.Path) {
"& { & '$($MyInvocation.MyCommand.Path)' $argList }"
} else {
"iex '& { $(irm https://github.com/ChrisTitusTech/winutil/releases/latest/download/winutil.ps1) } $argList'"
# Add the passed parameters to $argsList
$PSBoundParameters.GetEnumerator() | ForEach-Object {
$argsList += if ($_.Value -is [switch] -and $_.Value) {
"-$($_.Key)"
} elseif ($_.Value) {
"-$($_.Key) `"$($_.Value)`""
}
}

# Set the download URL for the latest release
$downloadURL = "https://github.com/ChrisTitusTech/winutil/releases/latest/download/winutil.ps1"

# Download the WinUtil script to '$env:TEMP'
try {
Write-Host "Downloading the latest stable WinUtil version..." -ForegroundColor Green
Invoke-RestMethod $downloadURL -OutFile "$env:TEMP\winutil.ps1"
} catch {
Write-Host "Error downloading WinUtil: $_" -ForegroundColor Red
break
}

$powershellcmd = if (Get-Command pwsh -ErrorAction SilentlyContinue) { "pwsh" } else { "powershell" }
$processCmd = if (Get-Command wt.exe -ErrorAction SilentlyContinue) { "wt.exe" } else { $powershellcmd }
# Setup the commands used to launch the script
$powershellCmd = if (Get-Command pwsh.exe -ErrorAction SilentlyContinue) { "pwsh.exe" } else { "powershell.exe" }
$processCmd = if (Get-Command wt.exe -ErrorAction SilentlyContinue) { "wt.exe" } else { $powershellCmd }

Start-Process $processCmd -ArgumentList "$powershellcmd -ExecutionPolicy Bypass -NoProfile -Command $script" -Verb RunAs
# Setup the script's launch arguments
$launchArguments = "-ExecutionPolicy Bypass -NoProfile -File `"$env:TEMP\winutil.ps1`" $argsList"
if ($processCmd -ne $powershellCmd) {
$launchArguments = "$powershellCmd $launchArguments"
}

# Set the title of the running PowerShell instance
$BaseWindowTitle = if ($MyInvocation.MyCommand.Path) {
$MyInvocation.MyCommand.Path
} else {
$MyInvocation.MyCommand.Definition
}

$Host.UI.RawUI.WindowTitle = if ($isElevated) {
$BaseWindowTitle + " (Admin)"
} else {
$BaseWindowTitle + " (User)"
}

# Relaunch the script as administrator if necessary
try {
if (!$isElevated) {
Write-Host "WinUtil is not running as administrator. Relaunching..." -ForegroundColor DarkCyan
Start-Process $processCmd -ArgumentList $launchArguments -Verb RunAs
break
} else {
Write-Host "Running the latest stable version of WinUtil as admin." -ForegroundColor DarkCyan
}
} catch {
Write-Host "Error launching WinUtil: $_" -ForegroundColor Red
break
}

# Start WinUtil transcript logging
$dateTime = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"

$logdir = "$env:localappdata\winutil\logs"
[System.IO.Directory]::CreateDirectory("$logdir") | Out-Null
Start-Transcript -Path "$logdir\winutil_$dateTime.log" -Append -NoClobber | Out-Null

# Set PowerShell window title
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Admin)"
clear-host
Start-Transcript -Path "$logdir\winutil_$dateTime.log" -Append -NoClobber | Out-Null
180 changes: 147 additions & 33 deletions windev.ps1
Original file line number Diff line number Diff line change
@@ -1,55 +1,169 @@
<#
.SYNOPSIS
This Script is used as a target for the https://christitus.com/windev alias.
It queries the latest winget release (no matter if Pre-Release, Draft or Full Release) and invokes It
This script is used as a target for the https://christitus.com/windev alias.
It queries the latest WinUtil release (no matter if it's a Pre-Release, Draft, or Full Release) and runs it.
.DESCRIPTION
This Script provides a simple way to always start the bleeding edge release even if it's not yet a full release.
This function should be run with administrative privileges.
Because this way of recursively invoking scripts via Invoke-Expression it might very well happen that AV Programs flag this because it's a common way of mulitstage exploits to run
This script provides a simple way to start the bleeding edge release even if it's not yet a full release.
This function can be run both with administrator and non-administrator permissions.
If it is not running as administrator, it will attempt to relaunch itself with administrator permissions.
The script no longer uses Invoke-Expression for its execution and now relies on Start-Process.
.EXAMPLE
irm https://christitus.com/windev | iex
Run in PowerShell > irm https://christitus.com/windev | iex
OR
Run in Admin Powershell > ./windev.ps1
Run in PowerShell > .\windev.ps1
OR
Run in PowerShell > iex "& { $(irm https://christitus.com/windev) } <arguments>"
OR
Run in PowerShell > .\windev.ps1 <arguments>
#>

# Function to fetch the latest release tag from the GitHub API
function Get-LatestRelease {
# Define the arguments for the WinUtil script; enables argument auto-completion.
param (
[switch]$Debug,
[string]$Config,
[switch]$Run
)

# Speed up download-related tasks by suppressing the output of Write-Progress.
$ProgressPreference = "SilentlyContinue"

# Determine whether or not the active process is currently running as administrator.
$ProcessIsElevated = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

# Function to get the latest stable or pre-release tag from the repository's releases page.
function Get-WinUtilReleaseTag {
# Retrieve the list of released versions from the repository's releases page.
try {
$releases = Invoke-RestMethod -Uri 'https://api.github.com/repos/ChrisTitusTech/winutil/releases'
$latestRelease = $releases | Where-Object {$_.prerelease -eq $true} | Select-Object -First 1
return $latestRelease.tag_name
$ReleasesList = Invoke-RestMethod 'https://api.github.com/repos/ChrisTitusTech/winutil/releases'
} catch {
Write-Host "Error fetching release data: $_" -ForegroundColor Red
return $latestRelease.tag_name
Write-Host "Error downloading WinUtil's releases list: $_" -ForegroundColor Red
break
}

# Filter through the released versions and select the first matching stable or pre-release version.
$StableRelease = $ReleasesList | Where-Object { -not $_.prerelease } | Select-Object -First 1
$PreRelease = $ReleasesList | Where-Object { $_.prerelease } | Select-Object -First 1

# Set the release tag based on the available releases; if no release tag is found use the 'latest' tag.
$ReleaseTag = if ($PreRelease) {
$PreRelease.tag_name
} elseif ($StableRelease) {
$StableRelease.tag_name
} else {
"latest"
}

# Return the release tag to facilitate the usage of the version number within other parts of the script.
return $ReleaseTag
}

# Get the latest stable or pre-release tag; falling back to the 'latest' release tag if no releases are found.
$WinUtilReleaseTag = Get-WinUtilReleaseTag

# Function to generate the URL used to download the latest release of WinUtil from the releases page.
function Get-WinUtilReleaseURL {
$WinUtilDownloadURL = if ($WinUtilReleaseTag -eq "latest") {
"https://github.com/ChrisTitusTech/winutil/releases/$($WinUtilReleaseTag)/download/winutil.ps1"
} else {
"https://github.com/ChrisTitusTech/winutil/releases/download/$($WinUtilReleaseTag)/winutil.ps1"
}

return $WinUtilDownloadURL
}

# Get the URL used to download the latest release of WinUtil from the releases page.
$WinUtilReleaseURL = Get-WinUtilReleaseURL

# Create the file path that the latest WinUtil release will be downloaded to on the local disk.
$WinUtilScriptPath = Join-Path "$env:TEMP" "winutil.ps1"

# Function to download the latest release of WinUtil from the releases page to the local disk.
function Get-LatestWinUtil {
if (!(Test-Path $WinUtilScriptPath)) {
Write-Host "WinUtil is not found. Downloading WinUtil '$($WinUtilReleaseTag)'..." -ForegroundColor DarkYellow
Invoke-RestMethod $WinUtilReleaseURL -OutFile $WinUtilScriptPath
}
}

# Function to redirect to the latest pre-release version
function RedirectToLatestPreRelease {
$latestRelease = Get-LatestRelease
if ($latestRelease) {
$url = "https://github.com/ChrisTitusTech/winutil/releases/download/$latestRelease/winutil.ps1"
# Function to check for any version changes to WinUtil, re-downloading the script if it has been upgraded/downgraded.
function Get-WinUtilUpdates {
# Make a web request to the latest WinUtil release URL and store the script's raw code for processing.
$RawWinUtilScript = (Invoke-WebRequest $WinUtilReleaseURL -UseBasicParsing).RawContent

# Create the regular expression pattern used to extract the script's version number from the script's raw content.
$VersionExtractionRegEx = "(\bVersion\s*:\s)([\d.]+)"

# Match the entire 'Version:' header and extract the script's version number directly using RegEx capture groups.
$RemoteWinUtilVersion = (([regex]($VersionExtractionRegEx)).Match($RawWinUtilScript).Groups[2].Value)
$LocalWinUtilVersion = (([regex]($VersionExtractionRegEx)).Match((Get-Content $WinUtilScriptPath)).Groups[2].Value)

# Check if WinUtil needs an update and either download a fresh copy or notify the user its already up-to-date.
if ([version]$RemoteWinUtilVersion -ne [version]$LocalWinUtilVersion) {
Write-Host "WinUtil is out-of-date. Downloading WinUtil '$($RemoteWinUtilVersion)'..." -ForegroundColor DarkYellow
Invoke-RestMethod $WinUtilReleaseURL -OutFile $WinUtilScriptPath
} else {
Write-Host 'Unable to determine latest pre-release version.' -ForegroundColor Red
Write-Host "Using latest Full Release"
$url = "https://github.com/ChrisTitusTech/winutil/releases/latest/download/winutil.ps1"
Write-Host "WinUtil is already up-to-date. Skipped update checking." -ForegroundColor Yellow
}
}

$script = Invoke-RestMethod $url
# Elevate Shell if necessary
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "Winutil needs to be run as Administrator. Attempting to relaunch."
# Function to start the latest release of WinUtil that was previously downloaded and saved to '$env:TEMP\winutil.ps1'.
function Start-LatestWinUtil {
param (
[Parameter(Mandatory = $false)]
[array]$WinUtilArgumentsList
)

$powershellcmd = if (Get-Command pwsh -ErrorAction SilentlyContinue) { "pwsh" } else { "powershell" }
$processCmd = if (Get-Command wt.exe -ErrorAction SilentlyContinue) { "wt.exe" } else { $powershellcmd }
# Setup the commands used to launch WinUtil using Windows Terminal or Windows PowerShell/PowerShell Core.
$PowerShellCommand = if (Get-Command pwsh.exe -ErrorAction SilentlyContinue) { "pwsh.exe" } else { "powershell.exe" }
$ProcessCommand = if (Get-Command wt.exe -ErrorAction SilentlyContinue) { "wt.exe" } else { $PowerShellCommand }

Start-Process $processCmd -ArgumentList "$powershellcmd -ExecutionPolicy Bypass -NoProfile -Command $(Invoke-Expression $script)" -Verb RunAs
# Setup the script's launch arguments based on the presence of Windows Terminal or Windows PowerShell/PowerShell Core:
# 1. Windows Terminal needs the name of the process to start ($PowerShellCommand) in addition to the launch arguments.
# 2. Windows PowerShell and PowerShell Core can receive and use the launch arguments as is without extra modification.
$WinUtilLaunchArguments = "-ExecutionPolicy Bypass -NoProfile -File `"$WinUtilScriptPath`""
if ($ProcessCommand -ne $PowerShellCommand) {
$WinUtilLaunchArguments = "$PowerShellCommand $WinUtilLaunchArguments"
}
else{
Invoke-Expression $script

# If WinUtil's launch arguments are provided, append them to the end of the list of current launch arguments.
if ($WinUtilArgumentsList) {
$WinUtilLaunchArguments += " " + $($WinUtilArgumentsList -join " ")
}

# If the WinUtil script is not running as administrator, relaunch the script with administrator permissions.
if (!$ProcessIsElevated) {
Write-Host "WinUtil is not running as administrator. Relaunching..." -ForegroundColor DarkCyan
Write-Host "Running the selected WinUtil release: Version '$($WinUtilReleaseTag)'." -ForegroundColor Green
Start-Process $ProcessCommand -ArgumentList $WinUtilLaunchArguments -Verb RunAs
} else {
Write-Host "Running the selected WinUtil release: Version '$($WinUtilReleaseTag)'." -ForegroundColor Green
Start-Process $ProcessCommand -ArgumentList $WinUtilLaunchArguments
}
}

# If WinUtil has not already been downloaded, attempt to download it and save it to '$env:TEMP\winutil.ps1'.
try {
Get-LatestWinUtil
} catch {
Write-Host "Error downloading WinUtil '$($WinUtilReleaseTag)': $_" -ForegroundColor Red
break
}

# Call the redirect function
# Attempt to check for WinUtil updates, if a version is released or removed this will re-download WinUtil.
try {
Get-WinUtilUpdates
} catch {
Write-Host "Error updating WinUtil '$($WinUtilReleaseTag)': $_" -ForegroundColor Red
break
}

RedirectToLatestPreRelease
# Attempt to start the latest release of WinUtil saved to the local disk; also supports WinUtil's arguments.
try {
if ($args) {
Start-LatestWinUtil $args
} else {
Start-LatestWinUtil
}
} catch {
Write-Host "Error launching WinUtil '$($WinUtilReleaseTag)': $_" -ForegroundColor Red
}