Is a User a Local Administrator?

Thomas

Q: Some of the things we do in our logon scripts require the user to be a local administrator. How can the script tell if the user is a local administrator or not, using PowerShell 7.

A: Easy using PowerShell 7 and the LocalAccounts module

Local Users and Groups

The simple answer is of course, easily. And since you ask, with PowerShell 7! But let’s begin lets begin by reviewing local users and groups in Windows.

Every Windows system, except for Domain Controllers, maintains a set of local accounts – local users and local groups. Domain controllers use the AD and do not really have local accounts as such. You use these local accounts in addition to domain users and domain groups on domain-joined hosts when setting permissions. You can log on to a given server using a local account or a domain account. On Domain Controllers you can only log in using a domain account.

As with AD groups, local groups and local users each have a unique Security ID (SID). When you give a local user or group access to a file or folder, Windows adds that SID to the object’s Access Control List. This is the same way Windows enables you to give permissions to a local file or folder to any Active Directory user or group.

Additionally, Windows and some Windows features create “well known” local groups. The intention is that you add users to these groups to enable those users to perform specific administrative functions on just those servers.

Traditionally, you might have used the Wscript.Network COM object, in conjunction with ADSI. You can, of course, use the older approach in side PowerShell 7, but why bother? The good news with PowerShell 7, you can use the Microsoft.PowerShell.LocalAccounts module to manage local accounts. At the time of writing, this is a Windows-only module.

The Microsoft.PowerShell.LocalAccounts module

In PowerShell 7 for Windows, you can use the Microsoft.PowerShell.LocalAccounts module to manage local users and group. This module is a Windows PowerShell module which PowerShell 7 loads from C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.LocalAccounts.

This module contains 15 cmdlets, which you can view like this:

PS> Get-Command -Module Microsoft.PowerShell.LocalAccounts

CommandType     Name                       Version    Source
-----------     ----                       -------    ------
Cmdlet          Add-LocalGroupMember       1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Disable-LocalUser          1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Enable-LocalUser           1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Get-LocalGroup             1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Get-LocalGroupMember       1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Get-LocalUser              1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          New-LocalGroup             1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          New-LocalUser              1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Remove-LocalGroup          1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Remove-LocalGroupMember    1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Remove-LocalUser           1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Rename-LocalGroup          1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Rename-LocalUser           1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Set-LocalGroup             1.0.0.0    Microsoft.PowerShell.LocalAccounts
Cmdlet          Set-LocalUser              1.0.0.0    Microsoft.PowerShell.LocalAccounts

As you can tell, these cmdlets allow you to add, remove, change, enable and disable a local user or local group And they allow you to add, remove and get the local group’s members. These cmdlets are broadly similar to the ActiveDirectory cmdlets, but work on local users. And as noted above, you can use domain users/groups as a member of a local group should you wish or need to.

You use the Get-LocalGroupMember command to view the members of a local group, like this:

PS> Get-LocalGroupMember -Group 'Administrators'

ObjectClass Name                     PrincipalSource
----------- ----                     ---------------
Group       COOKHAMDomain Admins    ActiveDirectory
User        COOKHAM24Administrator  Local
User        COOKHAMJerryG           ActiveDirectory
User        COOKHAM24Dave           Local

As you can see in this output, the local Administrators group on this host contains domain users and groups as well as local users

Is the User an Administrator?

It’s easy to get membership of any local group, as you saw above. But what if you want to find out if a given user is a member of some local administrative group? That too is pretty easy and take a couple of steps. One way you can get the name of the current user is by using whoami.exe. Then you can get the members of the local administrator’s group. Finally, you check to see if the currently logged on user is a member of the group. All of which looks like this:

PS> # Get who I am
PS> $Me = whoami.exe
PS> $Me 
Cookham\JerryG

PS> # Get members of administrators group
PS> $Admins = Get-LocalGroupMember -Name Administrators | 
       Select-Object -ExpandProperty name

PS> # Check to see if this user is an administrator and act accordingly
PS> if ($Admins -Contains $Me) {
      "$Me is a local administrator"} 
    else {
     "$Me is NOT a local administrator"}
Cookham\JerryG is a local administrator

If the administrative group contains a user running the script, then $Me is a user in that local admin group.

In this snippet, we just echo the fact that the user is, ir is not, a member of the local administrators group. You can adapt it to ensure a user is a member of the appropriate group before attempting to run certain commands. And you can also adapt it to check for membership in other local groups such as Backup Operators or Hyper-V Users which may be relevant.

In your logon script, once you know that the user is a member of a local administrative group, you can carry out any tasks that requires that membership. And if the user is not a member of the group, you could echo that fact, and avoid using the relevant cmdlets.

Summary

Using the Local Accounts module in PowerShell 7, it’s easy to manage local groups! You can, of course, manage the groups the same way in Windows PowerShell.

Tip of the Hat

This article was originally a VBS based solution as described in an earlier blog post. I am not sure who the author of the original post was – but thanks.

7 comments

Comments are closed. Login to edit/delete your existing comments

  • Fleet Command

    That’s not entirely in PowerShell. It cheats and uses WhoAmI.exe. Here is what I use:

      $MyId = [System.Security.Principal.WindowsIdentity]::GetCurrent()
      $WindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal( $MyId )
      return $WindowsPrincipal.IsInRole( [System.Security.Principal.WindowsBuiltInRole]::Administrator )
    

    My approach returns “false” if the current user is an admin but the current process is not elevated. Once can still use $MyID.Name instead of WhoAmI.exe though, like this:

      $MyId = [System.Security.Principal.WindowsIdentity]::GetCurrent()
      return $MyId.Name -in $(Get-LocalGroupMember -Name Administrators).Name
    
    • Daniel Taylor

      Q: Some of the things we do in our logon scripts require the user to be a local administrator. How can the script tell if the user is a local administrator or not, using PowerShell 7.

      A: Easy using PowerShell 7 and the LocalAccounts module.

      But… but… but… this has nothing to do with PowerShell 7. Though that was the question.

      This has been doable for well before PowerShell ever existed (including using legacy tools other than whoami.exe; WMIC, VBScript and WMI, ADSI), and even when it (Powershell) was there are articles from Microsoft folks/types showing this as far back as PowerShellv2 and beyond.

      https://www.hanselman.com/blog/how-to-determine-if-a-user-is-a-local-administrator-with-powershell

      https://devblogs.microsoft.com/scripting/check-for-admin-credentials-in-a-powershell-script

      The Local Group module is not unique to Powershell 7. PowerShell v5x has it as well, and in earlier versions, you can install the local users and groups module.

      Find-Module -Name '*localuser*'| Format-List
      
      
      Name                       : LocalUserManagement
      Version                    : 3.0
      Type                       : Module
      Description                : a module that performs various local user management functions
      Author                     : ed wilson
      CompanyName                : EdWilson
      Copyright                  : 2014
      PublishedDate              : 06-May-14 21:44:08
      • @DoctorDNS

        Thanks for your comment, Daniel.

        The local accounts module is found in the WIn32 folder – so is a Windows PowerShell module rather than a PowerShell module. And, some of us with long memories of the development of PowerShell 7.x may remember that what you say was not always the case. I closely monitored the development of PowerShell 7, and recall this GitHub issue https://github.com/PowerShell/PowerShell/issues/4305 (and its resolution). With the benefit of hindsight, I could have written this blog post to make that clearer.
        So yes, you can use the code in the post with both Windows PowerShell 5.1 and the latest versions of PowerShell 7.
        Thanks again for your comment and I hope you are enjoying the posts so far. And maybe consider creating a separate post on System.Security.Principal.WindowsPrincipal?

    • @DoctorDNS

      Thanks Fleet Command. You show another way to do it.

      I like using Whoami and am old school in that regard. Another way to create $Me would be:

      $MyId             = [System.Security.Principal.WindowsIdentity]::GetCurrent()
      $WindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal( $MyId )
      $Me               = $WindowsPrincipal.Identities.NAME

      Interestingly, using .NET in this way to create $Me is significantly faster then using Whowmi.exe:

      PSH [C:\Foo]: $SB1 = {
       $Me =  Whoami.exe
      }
      PSH [C:\Foo]: Measure-command -Expression $SB1
      
      TotalMilliseconds : 42.6657
      
      PSH [C:\Foo]: $me
      cookham\tfl
      
      PSH [C:\Foo]: $SB2 = {
        $MyId             = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $WindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal( $MyId )
        $Me               = $WindowsPrincipal.Identities.NAME
      }
      PSH [C:\Foo]: Measure-command -Expression $SB2
      
      TotalMilliseconds : 12.9862
      
      PSH [C:\Foo]: $me
      COOKHAM\tfl
      

      So there are (*at least) two ways to calculate $ME – both work and one is a lot slower.

      • Fleet Command

        With respect, why do you even create the $WindowsPrincipal object when you have no intentions of calling IsInRole()? $MyID.Name is the same as $WindowsPrincipal.Identities.Name. You’re just imposing a few milliseconds of performance penalty. This scripts demonstrates that:

        function StaticVoidMain {
            $SB0 = Measure-Command -Expression {
                $MyId = [System.Security.Principal.WindowsIdentity]::GetCurrent()
            }
            $SB1 = Measure-Command -Expression {
                $Me1 = $MyId.Name
            }
            $SB2 = Measure-Command -Expression {
                $WindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal( $MyId )
                $Me2 = $WindowsPrincipal.Identities.Name
            }
        
            Write-Output "Method 1: $($SB0.TotalMilliseconds + $SB1.TotalMilliseconds) milliseconds"
            $Me1
            Write-Output "Method 2: $($SB0.TotalMilliseconds + $SB2.TotalMilliseconds) milliseconds"
            $Me2
        }
        
        StaticVoidMain

        Here is the output:

        Method 1: 14.7724 milliseconds
        DOMINION\SarahKerrigan
        Method 2: 19.956 milliseconds
        DOMINION\SarahKerrigan

        • @DoctorDNS

          I love WordPress (at times). Fleet Command – I did not use $WindowsPrincipal in the original post. You may have been referring to comment vs the op. When I create code samples, I tend to use variables to hold output as they may come in useful later – and in a part of a script not shown here. But you ake a blood point that

          Looking at your function – I note that in the second method, you have two assignments, vs 1 for the first method. I recoded this as:
          PSH [LOGS:\Chapter 15 - WMI]: function StaticVoidMain {
          $SB0 = Measure-Command -Expression {
          $MyId = [System.Security.Principal.WindowsIdentity]::GetCurrent()
          }
          $SB1 = Measure-Command -Expression {
          $Me1 = $MyId.Name
          }
          $SB2 = Measure-Command -Expression {
          $Me2 = (New-Object System.Security.Principal.WindowsPrincipal( $MyId )).identities.Name
          }

          Write-Output "Method 1: $($SB0.TotalMilliseconds + $SB1.TotalMilliseconds) milliseconds"
          $Me1
          Write-Output "Method 2: $($SB0.TotalMilliseconds + $SB2.TotalMilliseconds) milliseconds"
          $Me2
          

          }

          StaticVoidMain
          Method 1: 2.74 milliseconds
          COOKHAM\tfl
          Method 2: 2.6983 milliseconds
          COOKHAM\tfl

          I tried this several times and on my host, what the second assignment removed, the difference is pretty small.

          And as an aside, you might like to author a post on this area – contact me if you are interested in authoring a post or two.

    • Thorsten Lambrecht

      I like this way of doing it rather going into local groups.
      The script on top misses UAC, which might not have the user with admin privileges the moment he starts the job.
      Yours does it in my eyes the right way.