.\Matthew Long

{An unsorted collection of thoughts}

Scripting Series – Interesting things you can do with VBScript and Powershell – Post 2, UAC Elevation

Posted by Matthew on March 6, 2011

In the first challenge in this series, I covered script self deletion.  In this post, i’m going to talk about dealing with UAC elevation in VB and Powershell scripts, way’s of detecting if we are running as an administrator, and how to trigger a request for elevation.  There are a lot of other ways of doing this, but these are two methods that I find work pretty well.

Firstly, a note on UAC Elevation and how it works.  Elevation is performed on a per-process basis, at initialisation,  so once a process has been started without administrative rights, the only way to gain those rights is to restart the process or launch a child process and request that it be granted admin rights.

The other important thing to remember, is that when a non-elevated process checks group memberships from a user context that does have admin rights, that user is not returned in the results set.  effectively, to non-elevated processes, no matter what user the process is run with, that user is not in any admin groups.

First up, VBScript.

Option Explicit
Dim App
If WScript.Arguments.length =0 then
  Set App = CreateObject("Shell.Application")
  App.ShellExecute "wscript.exe", Chr(34) & WScript.ScriptFullName & Chr(34) & " uac", "", "runas",1

Else

  'Perform Script Functions...

End If

WScript.Quit()

This is quite an elegant solution, if not the most efficient.  Essentially what the script does is first check to see if the script was started with an argument indicating we’ve run the process as an administrator explicitly.  If that argument is not found, we create a child process with the RunAs verb, and wait for that process to finish before we continue.  Starting the process with the RunAs verb will prompt for confirmation of administrative rights if we are not already in such a context.  The second process here is the WScript engine and our current VBScript’s path.  If our argument is found (in this case, the first argument uac) then rather than launching our child process, we instead carry on with our scripts main workload.

Obviously if your script accepts arguments, make sure you pass the other arguments onto your new process accordingly!  Note that in the above script, if you run the script with UAC off, or if you launch it the first time with admin rights, you won’t see a prompt and the script will just continue (but still create the second process).

Next up, Powershell.  As the Powershell process isn’t quite as  lightweight, we’ll do a check to see if this process is operating with the correct rights before trying to elevate.


     Function Test-CurrentAdminRights
     {
      #Return $True if process has admin rights, otherwise $False
      $user = [System.Security.Principal.WindowsIdentity]::GetCurrent()
      $Role = [System.Security.Principal.WindowsBuiltinRole]::Administrator
      return (New-Object Security.Principal.WindowsPrincipal $User).IsInRole($Role)
     }

The function Test-CurrentAdminRights checks to see if the user that the script (the powershell.exe process) is running under is in the Administrator role.  As I mentioned earlier as the user isn’t marked as being in the administrative groups unless the process is operating as an admin, this will only ever return True if the process is running under an administrative context.

Personally, if the function returns false i’d prefer to throw an exception or message back to the user to ask them to launch the script from an administrative console.  The reason for this is that when we launch a new powershell process it might not have access to the same snappins, variables, current working directory (administrative PS consoles start in C:\Windows\System32), etc.  However, the below function will elevate the current script if you need it to :

Function Invoke-AsAdmin()
{
    Param
         (
    [System.String]$ArgumentString = ""
         )
    $NewProcessInfo = new-object "Diagnostics.ProcessStartInfo"
    $NewProcessInfo.FileName = [System.Diagnostics.Process]::GetCurrentProcess().path    
    $NewProcessInfo.Arguments = "-file " + $MyInvocation.MyCommand.Definition + " $ArgumentString"
    $NewProcessInfo.Verb = "runas"
    $NewProcess = [Diagnostics.Process]::Start($NewProcessInfo)
    $NewProcess.WaitForExit()
}

Just pass in any arguments you need to this function, and it will create the necessary process.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s