I've been writing a powershell script that needs to self-elevate to admin. Self-elevation is the very last function I've added to the script after debugging the rest of it, and I get a type error when passing the parameters into the script. What seems to be happening is that during the self-elevation process, the boolean [System.Management.Automation.SwitchParameter]
type of the -Debug
parameter's value is getting typecast to [string]
, and I can't figure out a way to have it re-cast as type [bool]
. I get a similar error if the script somehow captures a whitespace string for the -NewIpdb
parameter, except it throws a validation error against the [System.IO.FileInfo]
type, even when the parameter hasn't been explicitly invoked by the user. I don't know how to make a powershell script not positionally capture arguments into named parameters if they are not explicitly invoked.
I'm using a solution found here to build a string of the original user-invoked parameters to pass into a modified version of this self-elevation solution but this comment on that answer only ambiguously advises that I would have to be "clever" about how I build the ArgumentList
. I have attempted use of the -Command
parameter for powershell.exe
as this post suggests but I still get the type error even with a few different methods of formatting the string to have it interpreted as a command expression. You can also see that I have already attempted to explicitly capture the True|False
values that switch parameters take and prefix them with a dollar sign to turn them into literal $true|$false
to no avail.
EDIT 1
I also just tried this solution that I saw suggested in the sidebar after posting this question, in combination with the capture-true/false trick to send only the switch parameter name and not an assigned value. Instead of getting an error in the admin powershell instance, it just straight-up quits.
TIDE
I'm clearly not "clever" and I am at an impasse and I need help.
Invocation (in user-level powershell window):
PS C:\Users\myname\Documents> .\changeip.ps1 -Debug
C:\Users\myname\Documents\changeip.ps1 -Debug:$True
[Debug, True]
PS C:\Users\myname\Documents>
PS C:\Users\myname\Documents> $PSVersionTable
Name Value
---- -----
PSVersion 5.1.19041.1682
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.19041.1682
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
The error in the Admin-level powershell window:
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Try the new cross-platform PowerShell https://aka.ms/pscore6
C:\Users\myname\Documents\changeip.ps1 : Cannot convert 'System.String' to the type
'System.Management.Automation.SwitchParameter' required by parameter 'Debug'.
+ CategoryInfo : InvalidArgument: (:) [changeip.ps1], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : CannotConvertArgument,changeip.ps1
PS C:\Windows\system32>
Relevant code, copy-pasted directly from the script:
# Parameters for command line usage, because some users may prefer to use this script in a command line lol
Param(
[Parameter(HelpMessage="Path to new IP database file for script to use")]
# https://4sysops.com/archives/validating-file-and-folder-paths-in-powershell-parameters/
[ValidateScript({
# If the valid-formatted path does not exist at all, throw an error
if( -Not ($_ | Test-Path) ){
throw "File does not exist"
}
# If the valid-formatted path does not point to a file, throw an error
if( -Not ($_ | Test-Path -PathType Leaf) ){
throw "Argument must point to a file"
}
# Finally, if the valid-formatted path does not point to a JSON file, specifically, throw an error
if($_ -notmatch "\.json"){
throw "Argument must point to a JSON file"
}
return $true
})] # Gotta catch 'em all! (The bracket types, that is)
# Data type that rejects invalid Windows file paths with illegal characters
[System.IO.FileInfo]$NewIpdb,
[Parameter(HelpMessage="A custom IP configuration string in the format IP,Netmask[,Gateway]")]
[ValidateScript({
# https://stackoverflow.com/questions/5284147/validating-ipv4-addresses-with-regexp
# Shortest validation regex used and modified slightly to validate a CSV list of 2-3 IPs
# This regex is reused in a helper function down below, but I can't use the function here in the Param() block for ease of input validation
if($_ -notmatch "^(((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4},?){2,3}$"){
throw "A comma-separated string of valid IP addresses must be provided in the order: IP,Netmask[,Gateway]"
}
return $true
})]
[string]$SetIP,
# A simple true/false flag that can reset the IP configuration
[Parameter(HelpMessage="Reset the network interface configured for this script to automatic DHCP configuration. Does not take an argument.")]
[switch]$Reset,
# A true/false flag that can restart the network interface
[Parameter(HelpMessage="Restart the network interface configured for this script. Does not take an argument.")]
[switch]$Restart,
# Used for elevation to admin privileges if script invoked without
# DO NOT INVOKE THIS FLAG YOURSELF. THIS FLAG GETS INVOKED INTERNALLY BY THIS SCRIPT.
[Parameter(HelpMessage="Used internally by script. Script MUST run with admin privileges, and attempts to self-elevate if necessary. This flag indicates success.")]
[switch]$Elevated
# Add parameters: -ListConfigs -SetConfig
)
# https://stackoverflow.com/questions/9895163/in-a-cmdlet-how-can-i-detect-if-the-debug-flag-is-set
# The -Debug common parameter doesn't set the value of a $Debug variable unlike user-defined parameters
# So this manual hack is here to fix that :/
$Debug = $PsBoundParameters.Debug.IsPresent
# https://stackoverflow.com/questions/21559724/getting-all-named-parameters-from-powershell-including-empty-and-set-ones
$parameters = ""
foreach($key in $MyInvocation.BoundParameters.keys) {
$parameters += "-" + $key + ":" + ("","$")[$MyInvocation.BoundParameters[$key] -imatch "true|false"] + $MyInvocation.BoundParameters[$key] + " "
}
#if($Debug) {
Write-Host $MyInvocation.MyCommand.Definition $parameters
Write-Host $MyInvocation.BoundParameters
#}
# Next two blocks are almost verbatim copypasta'd from:
# https://superuser.com/a/532109
# Modified slightly to add user-invoked parameters to the argument list
# Function to test if the current security context is Administrator
function Test-Admin {
$currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
$currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
# If the script is not running as Administrator...
if ((Test-Admin) -eq $false) {
# Check if elevation attempt has been made
if ($elevated) {
# tried to elevate, did not work, aborting
throw "Unable to elevate to Administrator privileges. This application cannot perform its designed function. Aborting."
}
else { # Try to elevate script
Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" {1} -elevated' -f ($myinvocation.MyCommand.Definition), $parameters)
}
exit
}
# 1260 more lines of code past this point, most of it building Windows Forms...