Manage local group membership on remote servers with PowerShell

From Svendsen Tech PowerShell Wiki
Jump to: navigation, search

Here is PowerShell code for adding/removing groups and users from local groups on remote servers, wrapped into two quite feature-complete functions that act similarly to native cmdlets when imported from the module.

The code is public domain, so you may do with it as you please.

The code is compatible with PowerShell version 2 and up.

You can manage groups with group policies (GPO), but the system was encumbered with flaws in that - last time I checked, which was in the 2008 R2 days (it's fixed now with GPO preferences for Win 7 / 2008 R2) - you couldn't enforce group membership without also excluding all other members not in your predefined list. I always thought this was curiously lacking as a feature/capability. I would have liked a check box that said "enforce these members, but do not remove others". I've been made aware there are Group Policy Preferences for 2008 R2 that indeed let you "Update membership" on the group (and also build group names using variables like "%domain%\Local_Admin_%computername%").

Regardless, in the wild, sometimes you need to add or remove members from local groups on remote servers - and sometimes it's a huge benefit to script it. That's why I decided to write this article about a simple script module that I wrote over three years ago now and rewrote and added features to this evening (it's almost 5 AM - I should in theory be in bed since I have work in the morning).

There's a -PSRemoting parameter that can also use different credentials (specify -Credential ($cred = Get-Credential)) in case that's helpful. Also remember runas.exe (runas /user:domain\username powershell.exe).

The code is basically just this, wrapped in convenience:

#([adsi]"WinNT://$env:COMPUTERNAME/Administrators,Group").Add("WinNT://ad.example.com/Role Local Admin Servers")

... and with .Remove() for removing.




Screenshot example

Adding-and-removing-an-AD-user-from-remote-servers-local-group.png

Download

STRemoteLocalGroupManagement.zip - download, unblock, unzip into a modules folder, Import-Module it. Tip: See $Env:PSModulePath.

If you have Windows Management Framework (WMF) version 5 and up, you can install it easily from the PowerShell gallery by simply running this command (requires an internet connection):

Install-Module -Name STRemoteLocalGroupManagement


PowerShell code

#requires -version 2
function Add-STLocalGroupADEntity {
    <#
    .SYNOPSIS
        Adds an AD user or group to a remote server's local group. By default the "Remote Desktop Users" group is
        targeted. Use -LocalGroup to specify a different group, like "Administrators".
        
        This code is compatible with PowerShell version 2 and up.

        Author: Joakim Borger Svendsen, 2014-12-03. Minor tweaking on 2017-03-31.
        The code is public domain, do with it as you please.

    .PARAMETER ComputerName
        Remote computer(s) on which to add user/group to a local group.
    .PARAMETER LocalGroup
        Local group name (on the remote computer). Default "Remote Desktop Users".
    .PARAMETER Domain
        Domain the AD entity belongs to. Long/dotted form.
    .PARAMETER Identity
        Group(s) or user(s) to add to the remote computer's local group (by default to its "Remote Desktop Users" group).
    .PARAMETER PSRemoting
        Use PowerShell remoting instead of remote ADSI.
    .PARAMETER Credential
        A PowerShell credentials object. Only used with PowerShell remoting. See Get-Help Get-Credential.
    .EXAMPLE
        Add-STLocalGroupADEntity -ComputerName 2012r2 -LocalGroup Administrators `
            -Domain whatever.local -Identity TestUser0001

        ComputerName : 2012r2
        Identity     : TestUser0001
        Success      : True
        Error        : 
        LocalGroup   : Administrators
        Domain       : whatever.local
    #>
    [CmdletBinding()]
    param(
        # Remote computer(s) on which to add user/group to a local group.
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [Alias('Cn', 'Name', 'PSComputerName')]
        [string[]] $ComputerName,
        # Local group name (on the remote computer). Default "Remote Desktop Users".
        [string] $LocalGroup = 'Remote Desktop Users',
        # Domain the AD entity belongs to. Long/dotted form.
        [Parameter(Mandatory=$true)]
        [string] $Domain,
        # AD group(s) or user(s) to add to the remote computer's local group (by default to its "Remote Desktop Users" group).
        [Parameter(Mandatory=$true)]
        [string[]] $Identity,
        # Use PowerShell remoting instead of remote ADSI.
        [switch] $PSRemoting,
        # A PowerShell credentials object. Only used with PowerShell remoting. See Get-Help Get-Credential.
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty)
    begin {
        # For use with PSRemoting.
        $ScriptBlock = {
            param(
                [string] $Computer,
                [string] $LocalGroup,
                [string] $Domain,
                [string[]] $Identity)
            foreach ($Id in $Identity) {
                Write-Verbose "Processing ${Computer} and ${Id}"
                $ErrorActionPreference = 'Stop'
                try {
                    ([adsi]"WinNT://${Computer}/${LocalGroup},Group").Add("WinNT://${Domain}/${Id}")
                    New-Object PSObject -Property @{
                        ComputerName = $Computer
                        Identity = $Id
                        Success = $true
                        Error = $null
                        LocalGroup = $LocalGroup
                        Domain = $Domain
                    }
                }
                catch {
                    New-Object PSObject -Property @{
                        ComputerName = $Computer
                        Identity = $Id
                        Success = $false
                        Error = $_.ToString() -replace '[\r\n]+', ' '
                        LocalGroup = $LocalGroup
                        Domain = $Domain
                    }
                }
                $ErrorActionPreference = 'Continue'
            }
        }
    }
    process {
        @(foreach ($Computer in $ComputerName) {
            #Write-Verbose -Message "Processing ${Computer} and ${Id}"
            if ($PSRemoting) {
                if ($Credential.Username -match '\S') {
                    Invoke-Command -ComputerName $Computer -ScriptBlock $ScriptBlock -ArgumentList $Computer, $LocalGroup, $Domain, $Identity -Credential $Credential
                }
                else {
                    Invoke-Command -ComputerName $Computer -ScriptBlock $ScriptBlock -ArgumentList $Computer, $LocalGroup, $Domain, $Identity
                }
            }
            else {
                foreach ($Id in $Identity) {
                    Write-Verbose "Processing ${Computer} and ${Id}"
                    $ErrorActionPreference = 'Stop'
                    try {
                        ([adsi]"WinNT://${Computer}/${LocalGroup},Group").Add("WinNT://${Domain}/${Id}")
                        New-Object PSObject -Property @{
                            ComputerName = $Computer
                            Identity = $Id
                            Success = $true
                            Error = $null
                            LocalGroup = $LocalGroup
                            Domain = $Domain
                        }
                    }
                    catch {
                        New-Object PSObject -Property @{
                            ComputerName = $Computer
                            Identity = $Id
                            Success = $false
                            Error = $_.ToString() -replace '[\r\n]+', ' '
                            LocalGroup = $LocalGroup
                            Domain = $Domain
                        }
                    }
                    $ErrorActionPreference = 'Continue'
                }
            }
        }) | Select-Object ComputerName, Identity, Success, Error, LocalGroup, Domain
    }
}

function Remove-STLocalGroupADEntity {
    <#
    .SYNOPSIS
        Removes an AD user or group from a remote server's local group. By default the "Remote Desktop Users" group is
        targeted. Use -LocalGroup to specify a different group, like "Administrators".

        This code is compatible with PowerShell version 2 and up.
        
        Author: Joakim Borger Svendsen, 2014-12-03. Revisited on 2017-03-31.
        The code is public domain, do with it as you please.

    .PARAMETER ComputerName,
        Remote computer(s) on which to remove user/group from a local group.
    .PARAMETER LocalGroup
        Local group name (on the remote computer). Default "Remote Desktop Users".
    .PARAMETER Domain
        Domain the AD entity belongs to. Long/dotted form.
    .PARAMETER Identity
        Group(s) or user(s) to remove from the remote computer's local group (by default its "Remote Desktop Users" group).
    .PARAMETER PSRemoting
        Use PowerShell remoting instead of remote ADSI.
    .PARAMETER Credential
        A PowerShell credentials object. Only used with PowerShell remoting. See Get-Help Get-Credential.
    
    .EXAMPLE
        Remove-STLocalGroupADEntity -ComputerName 2012r2 -LocalGroup "Administrators" `
            -Domain whatever.local -Identity TestUser0001 # Removing now

        ComputerName : 2012r2
        Identity     : TestUser0001
        Success      : True
        Error        : 
        LocalGroup   : Administrators
        Domain       : whatever.local
    #>
    [CmdletBinding()]
    param(
        # Remote computer(s) on which to remove user/group from a local group.
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [Alias('Cn', 'Name', 'PSComputerName')]
        [string[]] $ComputerName,
        # Local group name (on the remote computer). Default "Remote Desktop Users".
        [string] $LocalGroup = 'Remote Desktop Users',
        # Domain the -ADEntity belongs to. Long/dotted form.
        [Parameter(Mandatory=$true)]
        [string] $Domain,
        # AD group(s) or user(s) to remove from the remote computer's local group (by default its "Remote Desktop Users" group).
        [Parameter(Mandatory=$true)]
        [string[]] $Identity,
        # Use PowerShell remoting instead of remote ADSI.
        [switch] $PSRemoting,
        # A PowerShell credentials object. Only used with PowerShell remoting. See Get-Help Get-Credential.
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty)
    begin {
        # For use with PSRemoting.
        $ScriptBlock = {
            param(
                [string] $Computer,
                [string] $LocalGroup,
                [string] $Domain,
                [string[]] $Identity)
            foreach ($Id in $Identity) {
                Write-Verbose "Processing ${Computer} and ${Id}"
                $ErrorActionPreference = 'Stop'
                try {
                    ([adsi]"WinNT://${Computer}/${LocalGroup},Group").Remove("WinNT://${Domain}/${Id}")
                    New-Object PSObject -Property @{
                        ComputerName = $Computer
                        Identity = $Id
                        Success = $true
                        Error = $null
                        LocalGroup = $LocalGroup
                        Domain = $Domain
                    }
                }
                catch {
                    New-Object PSObject -Property @{
                        ComputerName = $Computer
                        Identity = $Id
                        Success = $false
                        Error = $_.ToString() -replace '[\r\n]+', ' '
                        LocalGroup = $LocalGroup
                        Domain = $Domain
                    }
                }
                $ErrorActionPreference = 'Continue'
            }
        }
    }
    process {
        @(foreach ($Computer in $ComputerName) {
            #Write-Verbose -Message "Processing ${Computer} and ${Id}"
            if ($PSRemoting) {
                if ($Credential.Username -match '\S') {
                    Invoke-Command -ComputerName $Computer -ScriptBlock $ScriptBlock -ArgumentList $Computer, $LocalGroup, $Domain, $Identity -Credential $Credential
                }
                else {
                    Invoke-Command -ComputerName $Computer -ScriptBlock $ScriptBlock -ArgumentList $Computer, $LocalGroup, $Domain, $Identity
                }
            }
            else {
                foreach ($Id in $Identity) {
                    Write-Verbose "Processing ${Computer} and ${Id}"
                    $ErrorActionPreference = 'Stop'
                    try {
                        ([adsi]"WinNT://${Computer}/${LocalGroup},Group").Remove("WinNT://${Domain}/${Id}")
                        New-Object PSObject -Property @{
                            ComputerName = $Computer
                            Identity = $Id
                            Success = $true
                            Error = $null
                            LocalGroup = $LocalGroup
                            Domain = $Domain
                        }
                    }
                    catch {
                        New-Object PSObject -Property @{
                            ComputerName = $Computer
                            Identity = $Id
                            Success = $false
                            Error = $_.ToString() -replace '[\r\n]+', ' '
                            LocalGroup = $LocalGroup
                            Domain = $Domain
                        }
                    }
                    $ErrorActionPreference = 'Continue'
                }
            }
        }) | Select-Object ComputerName, Identity, Success, Error, LocalGroup, Domain
    }    
}

Export-ModuleMember Add-STLocalGroupADIdentity, Remove-STLocalGroupADIdentity
#([adsi]"WinNT://$env:COMPUTERNAME/Administrators,Group").Add("WinNT://ad.example.com/Role Local Admin Servers")