Handy Function Could be Feature?

Topics: Developer Forum, User Forum
Jan 24 at 4:49 PM
Edited Jan 24 at 4:51 PM
I recently composed the following function, which I use as a reusable mechanism at the beginning of any function that requires elevated permissions (for instance, I have a single function that starts a bunch of services in my profile). It would probably need a little bit of tweaking to be proper as a feature, but thought the idea my spark some interest.

Effectively, this checks to see if the current shell is elevated. If it isn't the shell is re-launched wioth elevated permissions and the original function is immediately executed with all of its original parameters. There are some limitations in that the function must be in one of the auto-loaded profile scripts or in an always imported module since any manually loaded scripts won't exist in the new shell. Still, I thought maybe you might be able to work around that or maybe someone will find it useful.

Implementation:
function Global:Test-ShellElevated {
    # Get the ID and security principal of the current user account
    $myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent();
    $myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID);
 
    # Get the security principal for the Administrator role
    $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator;
 
    # Check to see if we are currently running "as Administrator"
    return $myWindowsPrincipal.IsInRole($adminRole);
    
}

function Global:Elevate-Self {
    param([switch]$Resume, [Hashtable] $Params, [switch]$NoExit)

    # This is a replacement for a fixed path in my proprietary environment
    # It is necessary that the elevated shell be able to find the path without passing it a variable
    $tmpFile = Join-Path "C:\Temp" "TempElevationArgs.xml";

    if ($Resume) {
        
        if (-not (Test-Path $tmpFile)) {
            throw "Elevate-Self -Resume should not be accessed directly";
        }

        $cmdSpec = Import-Clixml $tmpFile;
        Remove-Item $tmpFile -Force;
        
        $parms = "param(";
        $args = @();

        $comma = $false;

        foreach ($arg in $cmdSpec.Params.GetEnumerator()) {
            if ($comma) { $parms += ',' };
                
            $parms += '$' + $arg.Key;
            $args += ,$arg.Value;
                
            $comma = $true;
        }

        $parms += ")";

        $fullCmdText = "$parms; $($cmdSpec.Command) @PSBoundParameters";
        #Write-Host $fullCmdText;
        [ScriptBlock]$resCmd = [ScriptBlock]::Create($fullCmdText);
            
        Invoke-Command $resCmd -ArgumentList $args;


        return;
    }

    $call = (Get-PSCallStack | Select-Object -Skip 1 -First 1);
    if ($call -eq $null) {
        throw "Elevate-Self must be called from within a function.";
    }

    if (-not Test-ShellElevated) {
        
        $cmdSpec = @{ Command = $call.Command; Params = $Params };

        $cmdSpec | Export-Clixml $tmpFile;
        
        $proc = $null;
        
        try {
            $proc = Invoke-Elevated { Elevate-Self -Resume }
        } catch {
            $proc = $null;
        }

        if ($proc -eq $null) {
            Write-Host "Operation Canceled" -ForegroundColor Red;
            return $false;
        } else {
            if (-not $NoExit) { exit; }
            return $false;
        }
    }

    return $true;
}
Usage:
function Global:StartMyServices {
    if (-not (Elevate-Self -Params $PSBoundParameters)) {
        # User canceled elevation or -NoExit was applied but this is not the elevated shell
        return;
    }

    # Do some task that requires admin
    Start-Service BlahBlah*;
}
Coordinator
Jan 26 at 8:49 PM
Nice work! WRT Test-ShellElevated we have an existing test that is marginally more to type: :-)

114> Test-UserGroupMembership Administrators
False

The bit about restarting a script under a new, elevated is something I've also tackled on my blog. Great minds think alike, eh? :-) However, I'm hesitant to add scripts like this because the Invoke-Elevated function that is currently in PSCX has been particularly problematic. Seems like I'm fixing bugs in that implementation too often. :-)
Feb 3 at 11:10 PM
Ahh, good to know about the reusable test bits; I'm never excited to maintain things someone else will maintain for me ;)

As for your blog post: it's good to see others have similar needs. I'm going to take a couple tips from it--most notably setting the current working directory and testing for UAC--but I have some differences in the environment I've targeted. The -NoProfile doesn't work for me because I'm actually exposing this function in a profile script in which several functions require admin, but not all.

I didn't think chances were high that this would get rolled in, especially considering the requirement of passing $PSBoundParameters (which I was hoping to find a way around). I'd love to see them do something about this stuff directly in PowerShell's implementation somehow, but this does the trick in the meantime.

Thanks kindly for the feedback :)