RemoteLocalGroups.psm1

function Get-RemoteLocalGroup {
    <#
.SYNOPSIS
Retrieves local groups (or specific local group objects) from one or more remote Windows computers.
 
.DESCRIPTION
Get-RemoteLocalGroup queries local groups on remote systems using PowerShell Remoting (Invoke-Command).
You may select from common preset local groups (tab-complete/validation) and/or supply custom group names.
If no group names are supplied, the function returns all local groups on the remote host(s).
The function accepts optional credentials for remote connections.
 
.PARAMETER Computername
One or more target computer hostnames (or IPs). Required. Accepts pipeline input.
 
.PARAMETER LocalGroupPreset
Optional. One or more preset local group names (validated). Examples:
Administrators, Remote Desktop Users, Power Users, Backup Operators, Remote Management Users,
Event Log Readers, Print Operators, Guests. Use this parameter for convenience and tab-completion.
 
.PARAMETER LocalGroup
Optional. One or more custom local group names. Can be combined with -LocalGroupPreset.
 
.PARAMETER Credential
Optional. PSCredential used for remote connections if the current account does not have access to the targets.
If omitted, the current user's credentials are used.
 
.OUTPUTS
When no group names supplied: raw Get-LocalGroup output from each remote host.
When one or more group names supplied: PSCustomObject with properties:
 - ComputerName
 - Group
 - DisplayName
 - Description
 - SID
 - Error (null when successful, message string on failure)
 
.EXAMPLE
# List all local groups on two servers using current credentials
Get-RemoteLocalGroup -Computername srv01,srv02
 
.EXAMPLE
# Query Administrators and Remote Desktop Users using explicit credential
$cred = Get-Credential
Get-RemoteLocalGroup -Computername srv01 -LocalGroupPreset Administrators,'Remote Desktop Users' -Credential $cred
 
.EXAMPLE
# Query a mix of preset and custom group names
Get-RemoteLocalGroup -Computername srv01 -LocalGroupPreset Administrators -LocalGroup 'My Custom Group'
 
.NOTES
- Requires PowerShell Remoting enabled on targets and appropriate rights to query local groups.
- Uses Get-LocalGroup on the remote host; older OS versions may behave differently.
 
.LINK
Author: Luis Carrillo
GitHub: https://www.github.com/LuisCarrilloTech
#>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, HelpMessage = 'Target computer name(s)')]
        [ValidateNotNullOrEmpty()]
        [String[]]$Computername,

        [Parameter(Mandatory = $false, HelpMessage = 'Select one or more common local groups (preset)')]
        [ValidateSet(
            'Administrators',
            'Remote Desktop Users',
            'Power Users',
            'Backup Operators',
            'Remote Management Users',
            'Event Log Readers',
            'Print Operators',
            'Guests',
            IgnoreCase = $true
        )]
        [String[]]$LocalGroupPreset,

        [Parameter(Mandatory = $false, HelpMessage = 'Provide custom local group names (can be used with preset)')]
        [String[]]$LocalGroup,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Credentials for remote access')]
        [PSCredential]$Credential
    )

    begin {
        # Merge presets + custom groups; if none provided, $TargetLocalGroups stays $null to indicate "list all"
        $TargetLocalGroups = @()
        if ($LocalGroupPreset) {
            $TargetLocalGroups += $LocalGroupPreset
        }
        if ($LocalGroup) {
            $TargetLocalGroups += $LocalGroup
        }

        $TargetLocalGroups = $TargetLocalGroups |
        ForEach-Object { $_.ToString().Trim() } |
        Where-Object { $_ -ne '' } |
        Select-Object -Unique

        if ($TargetLocalGroups.Count -eq 0) {
            $TargetLocalGroups = $null
        }

        # Scriptblock for listing all groups
        $sbListAll = {
            try {
                Get-LocalGroup | Select-Object Name, Description, @{n = 'SID'; e = { $_.SID.Value } } | Sort-Object Name
            }
            catch {
                throw "Get-LocalGroup failed: $($_.Exception.Message)"
            }
        }

        # Scriptblock for specific group(s)
        $sbByName = {
            param($Groups)
            $out = @()
            foreach ($g in $Groups) {
                try {
                    $grp = Get-LocalGroup -Name $g -ErrorAction Stop
                    $out += [PSCustomObject]@{
                        ComputerName = $env:COMPUTERNAME
                        Group        = $g
                        DisplayName  = $grp.Name
                        Description  = $grp.Description
                        SID          = if ($grp.SID) {
                            $grp.SID.Value
                        }
                        else {
                            $null
                        }
                        Error        = $null
                    }
                }
                catch {
                    $out += [PSCustomObject]@{
                        ComputerName = $env:COMPUTERNAME
                        Group        = $g
                        DisplayName  = $null
                        Description  = $null
                        SID          = $null
                        #Error = $_.Exception.Message
                    }
                }
            }
            return $out
        }
    }

    process {
        foreach ($server in $Computername) {
            try {
                if (!($TargetLocalGroups)) {
                    if ($Credential) {
                        Invoke-Command -ComputerName $server -Credential $Credential -ScriptBlock $sbListAll -ErrorAction Stop
                    }
                    else {
                        Invoke-Command -ComputerName $server -ScriptBlock $sbListAll -ErrorAction Stop
                    }
                }
                else {
                    if ($Credential) {
                        Invoke-Command -ComputerName $server -Credential $Credential -ArgumentList ($TargetLocalGroups) -ScriptBlock $sbByName -ErrorAction Stop
                    }
                    else {
                        Invoke-Command -ComputerName $server -ArgumentList ($TargetLocalGroups) -ScriptBlock $sbByName -ErrorAction Stop
                    }
                }
            }
            catch {
                Write-Warning "Error gathering local group info for system: $server - $($_.Exception.Message)"
            }
        }
    }

}
function Get-RemoteLocalGroupMembers {
    <#
.SYNOPSIS
Retrieves local group members from one or more remote Windows computers.
 
.DESCRIPTION
Get-RemoteLocalGroupMembers queries specified local groups on remote systems using PowerShell Remoting (Invoke-Command).
You may select from common preset local groups (tab-complete/validation) and/or supply custom group names.
The function normalizes inputs, defaults to the 'Administrators' group when none are provided, and returns structured PSCustomObjects
that include per-machine, per-group results and any retrieval errors.
 
.PARAMETER Computername
One or more target computer hostnames (or IPs). Required. Accepts pipeline input.
 
.PARAMETER LocalGroupPreset
Optional. One or more preset local group names (validated). Examples: Administrators, Remote Desktop Users, Power Users, Backup Operators.
Use this parameter for convenience and tab-completion.
 
.PARAMETER LocalGroup
Optional. One or more custom local group names. Can be combined with -LocalGroupPreset.
 
.PARAMETER Credential
Optional. PSCredential used for remote connections if the current account does not have access to the targets.
 
.OUTPUTS
PSCustomObject with properties:
 - ComputerName
 - Group
 - Name (member display name)
 - ObjectClass (User / Group / SID type)
 - SID
 - Error (null when successful, message string on failure)
 
.EXAMPLE
# Query Administrators on two servers using current credentials
Get-RemoteLocalGroupMembers -Computername srv01,srv02 -LocalGroupPreset Administrators
 
.EXAMPLE
# Query multiple preset groups and a custom group using explicit credential
$cred = Get-Credential
Get-RemoteLocalGroupMembers -Computername srv01 -LocalGroupPreset 'Remote Desktop Users','Event Log Readers' -LocalGroup 'My Custom Group' -Credential $cred
 
.NOTES
- Requires PowerShell Remoting enabled on targets and appropriate rights to query local groups.
- Uses Get-LocalGroupMember on the remote host; older OS versions may not populate all properties (PrincipalSource supported on Win10/Server2016+).
- Returns per-group error information rather than terminating the entire run for a single failure.
 
.LINK
Author: Luis Carrillo
GitHub: https://www.github.com/LuisCarrilloTech
#>


    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 0,
            HelpMessage = 'Target computer name(s)')]
        [ValidateNotNullOrEmpty()]
        [String[]]$Computername,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Select one or more common local groups (preset)')]
        [ValidateSet(
            'Administrators',
            'Remote Desktop Users',
            'Power Users',
            'Backup Operators',
            'Remote Management Users',
            'Event Log Readers',
            'Print Operators',
            'Guests',
            IgnoreCase = $true
        )]
        [String[]]$LocalGroupPreset,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Provide custom local group names (can be used with preset)')]
        [String[]]$LocalGroup,

        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Credentials for remote access')]
        [PSCredential]$Credential
    )

    # Merge presets + custom, normalize and default to Administrators
    $TargetLocalGroups = @()
    if ($LocalGroupPreset) {
        $TargetLocalGroups += $LocalGroupPreset
    }
    if ($LocalGroup) {
        $TargetLocalGroups += $LocalGroup
    }

    $TargetLocalGroups = $TargetLocalGroups |
    ForEach-Object { $_.Trim() } |
    Where-Object { $_ -ne '' } |
    Select-Object -Unique

    if (-not $TargetLocalGroups -or $TargetLocalGroups.Count -eq 0) {
        $TargetLocalGroups = @('Administrators')
    }

    # Remote scriptblock accepts groups as argument and returns standardized objects
    $sb = {
        param($Groups)
        $out = @()
        foreach ($g in $Groups) {
            try {
                $members = Get-LocalGroupMember -Group $g -ErrorAction Stop
                foreach ($m in $members) {
                    $out += [PSCustomObject]@{
                        ComputerName = $env:COMPUTERNAME
                        Group        = $g
                        Name         = $m.Name
                        ObjectClass  = $m.ObjectClass
                        SID          = $m.SID
                        Error        = $null
                    }
                }
            }
            catch {
                $out += [PSCustomObject]@{
                    ComputerName = $env:COMPUTERNAME
                    Group        = $g
                    Name         = $null
                    ObjectClass  = $null
                    SID          = $null
                    Error        = $_.Exception.Message
                }
            }
        }
        return $out
    }

    foreach ($server in $Computername) {
        try {
            if ($Credential) {
                $results = Invoke-Command -ComputerName $server -ScriptBlock $sb -ArgumentList ($TargetLocalGroups) -Credential $Credential -ErrorAction Stop
            }
            else {
                $results = Invoke-Command -ComputerName $server -ScriptBlock $sb -ArgumentList ($TargetLocalGroups) -ErrorAction Stop
            }

            # Output results as table or objects
            $results | Select-Object ComputerName, Group, Name, ObjectClass, SID, Error | Format-Table -AutoSize
        }
        catch {
            Write-Warning "Error gathering local group info for system: $server - $($_.Exception.Message)"
        }
    }
}
function Add-RemoteLocalGroupMembers {
    <#
.SYNOPSIS
Adds one or more members to local groups on remote Windows computers.
 
.DESCRIPTION
Add-RemoteLocalGroupMembers adds specified account(s) to one or more local groups on remote systems using PowerShell Remoting (Invoke-Command).
You may select from common preset local groups (tab-complete/validation) and/or supply custom group names. The function normalizes inputs,
defaults to the 'Administrators' group when none are provided, and returns PSCustomObjects that include per-machine, per-group results and any errors.
 
.PARAMETER ComputerName
One or more target computer hostnames (or IPs). Accepts pipeline input.
 
.PARAMETER LocalGroupPreset
Optional. One or more preset local group names (validated). Examples:
Administrators, Remote Desktop Users, Power Users, Backup Operators, Remote Management Users,
Event Log Readers, Print Operators, Guests. Use this parameter for convenience and tab-completion.
 
.PARAMETER LocalGroup
Optional. One or more custom local group names. Can be combined with -LocalGroupPreset.
 
.PARAMETER Members
One or more user/group accounts to add to the target local groups. Provide DOMAIN\User or local user names as required.
 
.PARAMETER Credential
Optional. PSCredential used for remote connections if the current account does not have rights on the targets.
 
.OUTPUTS
PSCustomObject with properties:
 - ComputerName
 - Group
 - Member
 - Result (Success / Failed)
 - Error (null when successful, message string on failure)
 
.EXAMPLE
# Add DOMAIN\User1 and DOMAIN\User2 to Administrators on two servers
$cred = Get-Credential
Add-RemoteLocalGroupMembers -ComputerName srv01,srv02 -LocalGroupPreset Administrators -Members 'CORP\User1','CORP\User2' -Credential $cred
 
.EXAMPLE
# Add a custom group on a single server using current creds
Add-RemoteLocalGroupMembers -ComputerName srv03 -LocalGroup 'My App Admins' -Members 'svc-app'
 
.NOTES
- Requires PowerShell Remoting enabled on targets and appropriate rights to modify local groups.
- Uses Add-LocalGroupMember on the remote host; account names must be valid on the target (or domain-qualified).
.LINK
Author: Luis Carrillo
GitHub: https://www.github.com/LuisCarrilloTech
#>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [String[]]$ComputerName,

        [Parameter(Mandatory = $false, HelpMessage = 'Select one or more common local groups (preset)')]
        [ValidateSet(
            'Administrators',
            'Remote Desktop Users',
            'Power Users',
            'Backup Operators',
            'Remote Management Users',
            'Event Log Readers',
            'Print Operators',
            'Guests',
            IgnoreCase = $true
        )]
        [String[]]$LocalGroupPreset,

        [Parameter(Mandatory = $false, HelpMessage = 'Provide custom local group names (can be used with preset)')]
        [String[]]$LocalGroup,

        [Parameter(Mandatory = $true, HelpMessage = 'One or more members to add to the group(s)')]
        [String[]]$Members,

        [Parameter(Mandatory = $false, HelpMessage = 'Credential for remote session (optional)')]
        [PSCredential]$Credential
    )

    begin {
        # Build final target group list (presets + custom), normalize and default to Administrators
        $TargetLocalGroups = @()
        if ($LocalGroupPreset) {
            $TargetLocalGroups += $LocalGroupPreset
        }
        if ($LocalGroup) {
            $TargetLocalGroups += $LocalGroup
        }

        $TargetLocalGroups = $TargetLocalGroups |
        ForEach-Object { $_.ToString().Trim() } |
        Where-Object { $_ -ne '' } |
        Select-Object -Unique

        if (-not $TargetLocalGroups -or $TargetLocalGroups.Count -eq 0) {
            $TargetLocalGroups = @('Administrators')
        }

        # remote scriptblock: accepts Groups[] and Members[] and returns structured results
        $sb = {
            param($Groups, $Members)
            $out = @()
            foreach ($g in $Groups) {
                foreach ($m in $Members) {
                    try {
                        # Ensure group exists
                        $grp = Get-LocalGroup -Name $g -ErrorAction Stop
                        Add-LocalGroupMember -Group $g -Member $m -ErrorAction Stop
                        $out += [PSCustomObject]@{
                            ComputerName = $env:COMPUTERNAME
                            Group        = $g
                            Member       = $m
                            Result       = 'Success'
                            Error        = $null
                        }
                    }
                    catch {
                        $out += [PSCustomObject]@{
                            ComputerName = $env:COMPUTERNAME
                            Group        = $g
                            Member       = $m
                            Result       = 'Failed'
                            Error        = $_.Exception.Message
                        }
                    }
                }
            }
            return $out
        }
    }

    process {
        foreach ($server in $ComputerName) {
            try {
                if ($Credential) {
                    $res = Invoke-Command -ComputerName $server -ScriptBlock $sb -ArgumentList ($TargetLocalGroups, $Members) -Credential $Credential -ErrorAction Stop
                }
                else {
                    $res = Invoke-Command -ComputerName $server -ScriptBlock $sb -ArgumentList ($TargetLocalGroups, $Members) -ErrorAction Stop
                }

                # Emit results objects to pipeline
                $res | Select-Object ComputerName, Group, Member, Result, Error
            }
            catch {
                # Emit error object for this server
                [PSCustomObject]@{
                    ComputerName = $server
                    Group        = $null
                    Member       = $null
                    Result       = 'Failed'
                    Error        = $_.Exception.Message
                }
            }
        }
    }
}
function Remove-RemoteLocalGroupMembers {
    <#
.SYNOPSIS
Removes one or more members from local groups on remote Windows computers.
 
.DESCRIPTION
Remove-RemoteLocalGroupMembers removes specified account(s) from one or more local groups on remote systems using PowerShell Remoting (Invoke-Command).
You may select from common preset local groups (tab-complete/validation) and/or supply custom group names. The function normalizes inputs,
defaults to the 'Administrators' group when none are provided, and returns PSCustomObjects that include per-machine, per-group results and any errors.
 
.PARAMETER ComputerName
One or more target computer hostnames (or IPs). Accepts pipeline input.
 
.PARAMETER LocalGroupPreset
Optional. One or more preset local group names (validated). Examples:
Administrators, Remote Desktop Users, Power Users, Backup Operators, Remote Management Users,
Event Log Readers, Print Operators, Guests. Use this parameter for convenience and tab-completion.
 
.PARAMETER LocalGroup
Optional. One or more custom local group names. Can be combined with -LocalGroupPreset.
 
.PARAMETER Members
One or more user/group accounts to remove from the target local groups. Provide DOMAIN\User or local user names as required.
 
.PARAMETER Credential
Optional. PSCredential used for remote connections if the current account does not have rights on the targets.
 
.OUTPUTS
PSCustomObject with properties:
 - ComputerName
 - Group
 - Member
 - Result (Success / Failed)
 - Error (null when successful, message string on failure)
 
.EXAMPLE
# Remove DOMAIN\User1 and DOMAIN\User2 from Administrators on two servers
$cred = Get-Credential
Remove-RemoteLocalGroupMembers -ComputerName srv01,srv02 -LocalGroupPreset Administrators -Members 'CORP\User1','CORP\User2' -Credential $cred
 
.EXAMPLE
# Remove a custom group member on a single server using current creds
Remove-RemoteLocalGroupMembers -ComputerName srv03 -LocalGroup 'My App Admins' -Members 'svc-app'
 
.NOTES
- Requires PowerShell Remoting enabled on targets and appropriate rights to modify local groups.
- Uses Remove-LocalGroupMember on the remote host; account names must be valid on the target (or domain-qualified).
 
.LINK
Author: Luis Carrillo
GitHub: https://www.github.com/LuisCarrilloTech
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [String[]]$ComputerName,

        [Parameter(Mandatory = $false, HelpMessage = 'Select one or more common local groups (preset)')]
        [ValidateSet(
            'Administrators',
            'Remote Desktop Users',
            'Power Users',
            'Backup Operators',
            'Remote Management Users',
            'Event Log Readers',
            'Print Operators',
            'Guests',
            IgnoreCase = $true
        )]
        [String[]]$LocalGroupPreset,

        [Parameter(Mandatory = $false, HelpMessage = 'Provide custom local group names (can be used with preset)')]
        [String[]]$LocalGroup,

        [Parameter(Mandatory = $true, HelpMessage = 'One or more members to remove from the group(s)')]
        [String[]]$Members,

        [Parameter(Mandatory = $false, HelpMessage = 'Credential for remote session (optional)')]
        [PSCredential]$Credential
    )

    begin {
        $TargetLocalGroups = @()
        if ($LocalGroupPreset) {
            $TargetLocalGroups += $LocalGroupPreset
        }
        if ($LocalGroup) {
            $TargetLocalGroups += $LocalGroup
        }

        $TargetLocalGroups = $TargetLocalGroups |
        ForEach-Object { $_.ToString().Trim() } |
        Where-Object { $_ -ne '' } |
        Select-Object -Unique

        if (-not $TargetLocalGroups -or $TargetLocalGroups.Count -eq 0) {
            $TargetLocalGroups = @('Administrators')
        }

        $sb = {
            param($Groups, $Members)
            $out = @()
            foreach ($g in $Groups) {
                foreach ($m in $Members) {
                    try {
                        $grp = Get-LocalGroup -Name $g -ErrorAction Stop
                        Remove-LocalGroupMember -Group $g -Member $m -ErrorAction Stop
                        $out += [PSCustomObject]@{
                            ComputerName = $env:COMPUTERNAME
                            Group        = $g
                            Member       = $m
                            Result       = 'Success'
                            Error        = $null
                        }
                    }
                    catch {
                        $out += [PSCustomObject]@{
                            ComputerName = $env:COMPUTERNAME
                            Group        = $g
                            Member       = $m
                            Result       = 'Failed'
                            Error        = $_.Exception.Message
                        }
                    }
                }
            }
            return $out
        }
    }

    process {
        foreach ($server in $ComputerName) {
            try {
                if ($Credential) {
                    $res = Invoke-Command -ComputerName $server -ScriptBlock $sb -ArgumentList ($TargetLocalGroups, $Members) -Credential $Credential -ErrorAction Stop
                }
                else {
                    $res = Invoke-Command -ComputerName $server -ScriptBlock $sb -ArgumentList ($TargetLocalGroups, $Members) -ErrorAction Stop
                }

                $res | Select-Object ComputerName, Group, Member, Result, Error
            }
            catch {
                [PSCustomObject]@{
                    ComputerName = $server
                    Group        = $null
                    Member       = $null
                    Result       = 'Failed'
                    Error        = $_.Exception.Message
                }
            }
        }
    }
}