Add-ToLocalGroup function: Adds users or Groups to Local Computer groups


#region Function Add-ToLocalGroup
Function Add-ToLocalGroup {
<#
.SYNOPSIS
	Adds users or Groups to Local Computer groups
.DESCRIPTION
	Adds users or Groups to Local Computer groups (tested for Local Computer groups. Might also work for AD groups)
	Can also specify a SID for the Local Computer group
	Will NOT create group
.PARAMETER DomainAndAccountToAdd
	The Windows NT Account name you want to add specified in <domain>/<username> format.
	Use fully qualified account names (e.g., <domain>/<username>) instead of isolated names (e.g, <username>) because they are unambiguous and provide better performance.
	CAVEAT: If you use -DomainAndAccountToAdd and -DomainAndGroupToAdd at the same time, Only -DomainAndGroupToAdd will be used
.PARAMETER DomainAndGroupToAdd
	The Windows NT GROUP name you want to add specified in <domain>/<username> format.
	Use fully qualified account names (e.g., <domain>/<username>) instead of isolated names (e.g, <username>) because they are unambiguous and provide better performance.
.PARAMETER TargetDomainAndGroup
	The Windows NT GROUP name specified in <domain>/<username> format that will get new members 
	Use fully qualified account names (e.g., <domain>/<username>) instead of isolated names (e.g, <username>) because they are unambiguous and provide better performance.
	CAVEAT: If you use -TargetDomainAndGroup and -TargetSID at the same time, Only -TargetSID will be used
.PARAMETER TargetSID
	The Windows SID of the Windows NT GROUP name that will get new members
	Specifying SIDs is best when dealing with multilingual environments
	"Remote Desktop Users" = S-1-5-32-555 = "Utilisateurs du Bureau à distance"
.EXAMPLE
	Add-ToLocalGroup -DomainAndGroupToAdd 'SIGNET/Domain Users' -TargetDomainAndGroup "$($env:COMPUTERNAME)/Remote Desktop Users" 
.EXAMPLE
	Add-ToLocalGroup -DomainAndGroupToAdd 'SIGNET/Domain Users' -TargetDomainAndGroup "./Remote Desktop Users"
.EXAMPLE
	Add-ToLocalGroup -DomainAndAccountToAdd 'SIGNET/stpierdj' -TargetDomainAndGroup "./Remote Desktop Users"
.EXAMPLE
	Add-ToLocalGroup -DomainAndAccountToAdd 'SIGNET/stpierdj' -TargetDomainAndGroup "./Remote Desktop Users"
.EXAMPLE
	Add-ToLocalGroup -DomainAndGroupToAdd 'SIGNET/Domain Users' -TargetSID 'S-1-5-32-555' #"Remote Desktop Users"
.NOTES
	Author: Denis St-Pierre (Ottawa, Canada)

	TODO: redo Error handling
.LINK
	List of Well Known SIDs: http://msdn.microsoft.com/en-us/library/system.security.principal.wellknownsidtype(v=vs.110).aspx
#>
	[CmdletBinding()]
	Param (
		[Parameter(Mandatory=$false,HelpMessage="<Domain>/<UserName>")]
		[ValidateNotNullorEmpty()]
		[String]$DomainAndAccountToAdd,	#Need to test for Forward Slash, if no slash at all, add current domain
		[Parameter(Mandatory=$false,HelpMessage="<Domain>/<GroupName>")]
		[ValidateNotNullorEmpty()]
		[String]$DomainAndGroupToAdd,	#Need to test for Forward Slash, if no slash at all, add current domain
		[Parameter(Mandatory=$false,HelpMessage="./<LocalGroupName> or <Domain>/<GroupName>")]
		[ValidateNotNullorEmpty()]
		[String]$TargetDomainAndGroup,
		[Parameter(Mandatory=$false,HelpMessage="Windows SID of the Windows NT GROUP name that will get new members")]
		[ValidateNotNullorEmpty()]
		[String]$TargetSID,				#S-1-5-32-555
		[Parameter(Mandatory=$false)]
		[ValidateNotNullOrEmpty()]
		[boolean]$ContinueOnError = $true
	)
    Begin {
        ## Get the name of this function and write header
        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
    }
    Process {
		If ($TargetSID) {
			Write-Log "Translating SID [$TargetSID] to OS language specific Account or Group Name" -Source ${CmdletName}
			$objSID	= New-Object System.Security.Principal.SecurityIdentifier($TargetSID)
			$objName = $objSID.Translate( [System.Security.Principal.NTAccount])
			[String]$TargetDomainAndGroup = $objName.Value		#e.g. BUILTIN\Remote Desktop Users
			$TargetDomainAndGroup = $TargetDomainAndGroup.Replace('\','/')	# flip \ to Forward slash (/)  for WinNT://
			$TargetDomainAndGroup = $TargetDomainAndGroup.Replace('BUILTIN/',"$($env:COMPUTERNAME)/")	# flip BUILTIN to %COMPUTERNAME% for WinNT://
		}
		
		#NOTE: [ADSI] hates adding already existing members, so we check first.
		$TargetGroupObj = [ADSI]("WinNT://$TargetDomainAndGroup,group")
		Write-Log "Getting members of the [$TargetDomainAndGroup] Group" -Source ${CmdletName}
		$MembersOfTargetGroupObj = @($TargetGroupObj.psbase.Invoke("Members"))	#Create Array of members SLOW?!
		#$MembersOfTargetGroupObj.GetType() | Get-Member # Show all member properties
		
		Write-Log "Getting Group Member NAMES..." -Source ${CmdletName}
		Try {
			$MemberNames = @($MembersOfTargetGroupObj | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)})
		} catch {
			[string]$ErrorMessage = "$($_.Exception.Message) $($_.ScriptStackTrace) $($_.Exception.InnerException)"
			Write-Log $ErrorMessage -Severity 3 -Source ${CmdletName}
			Write-Log " ERROR while getting names of each group member. `r`n[$ErrorMessage]" -Severity 3 -Source ${CmdletName}
			If ($ContinueOnError) {
				Return $null
			} else {
				[string]$ErrorMessage = "$($_.Exception.Message) $($_.ScriptStackTrace) $($_.Exception.InnerException)"
				Write-Log $ErrorMessage -Severity 3 -Source ${CmdletName}
				Throw "ERROR while getting names of each group member. `r`n[$ErrorMessage]"
			}	
		}
		
		If ($DomainAndAccountToAdd) {		#Adding a user account to the Group
			$AccountToAdd = $DomainAndAccountToAdd.Substring($DomainAndAccountToAdd.IndexOf('/')+1) #Right() +1
			If ($MemberNames -contains $AccountToAdd) { 
				Write-Log " [$AccountToAdd] is already a member of [$TargetDomainAndGroup] group" -Severity 2 -Source ${CmdletName}
			} Else {
				Write-Log " Adding Account [$AccountToAdd] to [$TargetDomainAndGroup] group" -Source ${CmdletName}
				Try {
					$TargetGroupObj.add("WinNT://$DomainAndAccountToAdd,user").path	#CAVEAT: Forward slash (/) (Opposite of GUI and VBS)
					Write-Log " Success." -Source ${CmdletName}
				} Catch {
					[string]$ErrorMessage = "$($_.Exception.Message) $($_.ScriptStackTrace) $($_.Exception.InnerException)"
					Write-Log $ErrorMessage -Severity 3 -Source ${CmdletName}
					Write-Log " ERROR: adding account [$AccountToAdd] failed. `r`n[$ErrorMessage]" -Severity 3 -Source ${CmdletName}
					If ($ContinueOnError) {
						Return $null
					} else {
						Throw "ERROR: adding account [$AccountToAdd] failed. `r`n[$ErrorMessage]"
					}					
				}
			}
		
		} Else {							#Adding a Group to the Group
			$GroupToAdd = $DomainAndGroupToAdd.Substring($DomainAndGroupToAdd.IndexOf('/')+1) #Right() +1
			If ($MemberNames -contains $GroupToAdd) { 
				Write-Log " [$GroupToAdd] is already a member of [$TargetDomainAndGroup] group" -Severity 2 -Source ${CmdletName}
			} Else {
				Write-Log " Adding Group [$GroupToAdd] to [$TargetDomainAndGroup] group" -Source ${CmdletName}
				Try {
	#				$TargetGroupObj.add("WinNT://$DomainAndGroupToAdd,group").path	#WORKS! Forward slash in PowerShell (Opposite of GUI and VBS)
					$adgroup =	[ADSI]"WinNT://$DomainAndGroupToAdd"	#"corp.domain.com/$admingroup"
	            	$TargetGroupObj = [ADSI]"WinNT://$TargetDomainAndGroup"  #"$server/Administrators"
	            	$TargetGroupObj.PSBase.Invoke("Add",$adgroup.PSBase.Path)	#Need to be Elevated to do this!
					Write-Log " Success." -Source ${CmdletName}
				} Catch {
					[string]$ErrorMessage = "$($_.Exception.Message) $($_.ScriptStackTrace) $($_.Exception.InnerException)"
					Write-Log $ErrorMessage -Severity 3 -Source ${CmdletName}
					Write-Log " ERROR: adding Group [$GroupToAdd] failed. `r`n[$ErrorMessage]" -Severity 3 -Source ${CmdletName}
					If ($ContinueOnError) {
						Return $null
					} Else {
						Throw "ERROR: adding Group [$GroupToAdd] failed. `r`n[$ErrorMessage]"
					}
				}
			}
		}
	}
    End {
        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
    }
}
#endregion Function Add-ToLocalGroup