Summary: By using Windows PowerShell splatting, domain users can be added to a local group. This script includes a function to convert a CSV file to a hash table.
Hey, Scripting Guy! I need to be able to use Windows PowerShell to add domain users to local user groups. I have been able to find VBScript examples, but no Windows PowerShell examples of doing this. When I looked through the Active Directory cmdlets, I could not find a cmdlet to do this. Can you provide some assistance?
— FB
Hello FB,
Microsoft Scripting Guy Ed Wilson here. I had a good talk with my nonscripting brother last night. He is all excited about his new book that is about some baseball player. I have not watched baseball for years, and as a result have forgotten most of what I knew about the sport. Not so with my little brother. He played college ball and coaches little league. I will buy his new book when it comes out, but I doubt if it will make me start watching baseball again. To me a “home run” is when I write a Windows PowerShell script and it runs correctly the first time. When that happens, if you peek into my office you will see jumping up and down, hear hooting and whooping, and even hear faint strains of a song from Queen.
FB, today was not one of those home run days. In fact, you could more appropriately characterize it as an infield fly, or perhaps a one-hopper into a double play. If I had been pitching, I would have been yanked before the third inning. What was the problem? I am so embarrassed. I should have caught it way sooner. The problem was a difference between the user name, user display name, and the sAMAccountName of the domain user. The displayName and the name attributes are shown in the following image.
The sAMAccountName attribute is shown in the following image, and it does not have a space in the name—the other attributes do have spaces in them. This caused the import of the users to fail.
Keep in mind that it only takes two lines of code to add a domain user to a local group. The essential two lines are shown here:
$de = [ADSI]“WinNT://$computer/$Group,group”
$de.psbase.Invoke(“Add”,([ADSI]“WinNT://$domain/$user”).path)
The remaining code in the script tests to ensure that the script is running with administrator rights, reads a CSV file, converts it to a hash table, and finally adds the domain users to the local group. The complete Add-DomainUserToLocalGroup.ps1 script is shown here.
Add-DomainUserToLocalGroup.ps1
Function Add-DomainUserToLocalGroup
{
[cmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[string]$computer,
[Parameter(Mandatory=$True)]
[string]$group,
[Parameter(Mandatory=$True)]
[string]$domain,
[Parameter(Mandatory=$True)]
[string]$user
)
$de = [ADSI]“WinNT://$computer/$Group,group”
$de.psbase.Invoke(“Add”,([ADSI]“WinNT://$domain/$user”).path)
} #end function Add-DomainUserToLocalGroup
Function Convert-CsvToHashTable
{
Param([string]$path)
$hashTable = @{}
import-csv -path $path |
foreach-object {
if($_.key -ne “”)
{
$hashTable[$_.key] = $_.value
}
Else
{
Return $hashtable
$hashTable = @{}
}
}
} #end function convert-CsvToHashTable
function Test-IsAdministrator
{
<#
.Synopsis
Tests if the user is an administrator
.Description
Returns true if a user is an administrator, false if the user
is not an administrator
.Example
Test-IsAdministrator
.Notes
NAME: Test-IsAdministrator
AUTHOR: Ed Wilson
LASTEDIT: 5/20/2009
KEYWORDS:
.Link
Http://www.ScriptingGuys.com
#Requires -Version 2.0
#>
param()
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
(New-Object Security.Principal.WindowsPrincipal $currentUser).IsInRole(`
[Security.Principal.WindowsBuiltinRole]::Administrator)
} #end function Test-IsAdministrator
# *** Entry point to script ***
#Add-DomainUsersToLocalGroup -computer mred1 -group HSGGroup -domain nwtraders -user bob
If(-not (Test-IsAdministrator))
{ “Admin rights are required for this script” ; exit }
Convert-CsvToHashTable -path C:\fso\addUsersToGroup.csv |
ForEach-Object { Add-DomainUserToLocalGroup @_ }
The Add-DomainUserToLocalGroup function requires four parameters: computer, group, domain, and user. Each of these parameters is mandatory, and an error will be raised if one is missing. The WinNT provider is used to connect to the local group. After the connection has been made to the local group, the invoke method from the base object is used to add the domain user to the local group. The Add-DomainUserToLocalGroup function is shown here:
Function Add-DomainUserToLocalGroup
{
[cmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[string]$computer,
[Parameter(Mandatory=$True)]
[string]$group,
[Parameter(Mandatory=$True)]
[string]$domain,
[Parameter(Mandatory=$True)]
[string]$user
)
$de = [ADSI]“WinNT://$computer/$Group,group”
$de.psbase.Invoke(“Add”,([ADSI]“WinNT://$domain/$user”).path)
} #end function Add-DomainUserToLocalGroup
The Convert-CsvToHashTable function is used to import a CSV file and to convert it to a series of hash tables. Each user to be added to the local group will form a single hash table. Therefore, if 15 users are to be added to a local group, 15 hash tables will be created. The CSV file, shown in the following image, is made of only two columns. The key and the value correspond to the two properties of a hash table.
A blank line is required to exist between each group of data, and a single blank line must exist at the bottom of the CSV file. This is because I told the script to look for a blank line to delineate the groups of data. This is seen in this section of the function.
if($_.key -ne “”)
{
$hashTable[$_.key] = $_.value
}
If a blank line is found, the hash table contained in the $hashtable variable is returned to the calling script. The hash table in the $hashtable variable is then recreated, which wipes out the data from the previous hash table. This is shown here:
Else
{
Return $hashtable
$hashTable = @{}
}
The complete Convert-CsvToHashTable function is shown here:
Function Convert-CsvToHashTable
{
Param([string]$path)
$hashTable = @{}
import-csv -path $path |
foreach-object {
if($_.key -ne “”)
{
$hashTable[$_.key] = $_.value
}
Else
{
Return $hashtable
$hashTable = @{}
}
}
} #end function convert-CsvToHashTable
The Test-IsAdministrator function determines if the script is running with elevated permissions or not. The Windows PowerShell script must be running in an elevated Windows PowerShell console or elevated Windows PowerShell ISE to complete successfully. If it is not elevated, the script will fail, even if the user running the script is an administrator. Because of this potential issue, the Test-IsAdministrator function is employed. If it is, the function returns true. This is the same function I have used in several other scripts and will not be discuss here. The complete Test-IsAdministrator function is shown here:
function Test-IsAdministrator
{
<#
.Synopsis
Tests if the user is an administrator
.Description
Returns true if a user is an administrator, false if the user
is not an administrator
.Example
Test-IsAdministrator
.Notes
NAME: Test-IsAdministrator
AUTHOR: Ed Wilson
LASTEDIT: 5/20/2009
KEYWORDS:
.Link
Http://www.ScriptingGuys.com
#Requires -Version 2.0
#>
param()
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
(New-Object Security.Principal.WindowsPrincipal $currentUser).IsInRole(`
[Security.Principal.WindowsBuiltinRole]::Administrator)
} #end function Test-IsAdministrator
One way to use the script is to only call the Add-DomainUsersToLocalGroup function. You can pass the parameters directly to the function as shown here. This line is commented out in the script and is for illustration purposes:
Add-DomainUsersToLocalGroup -computer mred1 -group HSGGroup -domain nwtraders -user bob
The really cool thing about the Add-DomainUserToLocalGroup.ps1 script is the way I call the Add-DomainUserToLocalGroup function. What I do is use a technique called splatting. The splatting operator is new for Windows PowerShell 2.0 (I will have a whole series of Hey, Scripting Guy! Blog posts in a few weeks about splatting, but it is so cool, I could not wait.)
Basically when using splatting, you pass a hash table to a function or to a Windows PowerShell cmdlet instead of having to directly supply the parameters. The DemoSplatting.ps1 script illustrates this.
DemoSplatting.ps1
$hashtable = @{“computername” = “localhost”; “class”=“win32_bios”}
Get-WmiObject @hashtable
When the DemoSplatting.ps1 script runs, the output appears that is shown in the following image.
The advantage is the ability to avoid having to align each of the parameters up individually when calling the function. The only bad thing is that the parameters and values must be passed as a hash table. Therefore, it was necessary to write the Convert-CsvToHashTable function. But now, that function can be used in other places where I wish to use splatting to call a function. The code that calls the Convert-CsvToHashTable function and pipes the resulting hash table to the Add-DomainUserToLocalGroup is shown here:
Convert-CsvToHashTable -path C:\fso\addUsersToGroup.csv | ForEach-Object { Add-DomainUserToLocalGroup @_ }
After the script has run, the local computer management tool is used to inspect the group to see if the users have been added. As shown in the following image, it worked! WooHOO! Yes!!!
Well, FB, it was bottom of the ninth with two people on base, two outs, and the count was three and two, but I finally hit a home run! Okay, maybe it was more like a ground ball. That is all there is to using Windows PowerShell to add domain users to local groups. This also concludes User Management Week. Join us tomorrow for Quick-Hits Friday.
We invite you follow us on Twitter and Facebook. If you have any questions, send email to us at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson and Craig Liebendorfer, Scripting Guys
I just came across this article as I am converting some VBScript to PowerShell. I have an issue where somehow my return value is getting modified with an extra space on the front. Below is a trimmed down version of my code. I want to pass back success or fail when trying to add the domain local groups to my server local groups. For testing I even changed my code to just return the word...
I am not sure why my reply is getting reformatted. I typed in the script line by line but it is getting re-formatted to a paragraph. I will keep trying to format it.
I did more research and found that the return command does not work like other languages. It returns all output in the function. I am now using reference variables.