New-ADUser + OtherAttributes w/ Splatting for Readability - powershell

foreach ($Person in $People) {
$NewUserParams = #{
Name = $Person.Name
Server = 'xxxx.com:389'
Path = 'CN=Users,CN=addressBook,DC=xxxx,DC=com'
Credential = $Credentials
givenName = $Person.givenName
otherAttributes = #{sn=$Person.sn}
}
New-ADUser #NewUserParams
}
I have many additional attributes (otherAttributes) that I would like to add that are available to me in the formart New-ADUser -Name XXX -OtherAttributes #{sn=xxx} . However, I am trying to using splatting to make the OtherAttributes more readable, along with other required parameters. I don't need to using splatting for the entire command, my goal was to break up otherAttributes so it wasn't a long string that wrapped. Ideas?

The value of otherAttributes is just another hashtable, and can be wrapped like any other hashtable:
$NewUserParams = #{
'Name' = $Person.Name
'Server' = 'server.example.com:389'
'Path' = 'cn=Users,cn=addressBook,dc=example,dc=com'
'Credential' = $Credentials
'givenName' = $Person.givenName
'otherAttributes' = #{
'sn' = $Person.sn
}
}
Personally, I recommend putting the keys of the hashtables in quotes to avoid surprises, but at least in the above example that's not required.
If the above doesn't work for you you need to provide more details about the code you're running and the error(s) you're getting. Displaying the content of the generated hashtable inside the loop usually helps troubleshooting problems with particular values.

Related

I am unable to get splatting to work with New-ADUser using given and created attributes

I am processing an array of AD User data pulled from one domain to recreate in another. I have created a hash table linking the New-ADUser parameters with the user data imported from a CSV (populated from the domain I intend to recreate). When I call New-ADUser with the hash table, the user is not created and there are no error.
Here is the hash table:
$NewUserAttr = #{
'Name' = $ADUser.UsersName
'SamAccountName' = $ADUser.UsersSamAccountName
'Company' = $ADUser.UsersCompany
'Department' = $ADUser.UsersDepartment
'DisplayName' = $ADUser.UsersDisplayName
'EmailAddress' = $ADUser.UsersMail
'EmployeeID' = $ADUser.UsersEmployeeID
'Enabled' = $UsersEnabled
'GivenName' = $ADUser.UsersGivenName
'Initials' = $ADUser.UsersInitials
'Manager' = $ADUser.Manager
'MobilePhone' = $ADUser.UsersMobileNum
'OfficePhone' = $ADUser.UsersTelephoneNumber
'PostalCode' = $ADUser.UsersPostalCode
'State' = $ADUser.UsersST
'StreetAddress' = $ADUser.UsersStreetAddress
'Surname' = $ADUser.UsersSN
'Title' = $ADUser.UsersTitle
'userPrincipalname' = $ADUser.UsersUPN
'Path' = $ParentOU
'Server' = $TargetDomain
'OtherAttr' = #{
'c' = $ADUser.Usersc
'GIDNumber' = $ADUser.UsersGIDNumber
'l' = $ADUser.UsersL
'LoginShell' = $ADUser.UsersLoginShell
'msSFU30Name' = $ADUser.UsersMsSFU30Name
'msSFU30NisDomain' = $ADUser.UsersMsSFU30NisDomain
'PhysicalDeliveryOfficeName' = $ADUser.UsersPhysicalDeliveryOfficeName
'SSN' = $ADUser.UsersSSN
'Uid' = $ADUser.UsersUid
'uidNumber' = $ADUser.UsersUidNum
'unixHomeDirectory' = $ADUser.UsersUHD
}
}
PS > New-ADUser #NewUserAttr
I have reduced the NewUserAttr to Name, SamAccountName, Path, and Server and that did create the user, but that is far less parameters than what I need.
Continuing from my comments:
To avoid empty fields from being assembled in the attributes Hashtable to use for splatting, you could create two lookup tables in which you map the CSV header names with the actual AD user attribute names.
Something like this:
# create two mapping lookup Hashtables for 'normal' attributes and one for the OtherAttributes
# format is: PropertyNameFromCsv = PropertyNameForActiveDirectory
$attribMap = #{
UsersName = 'Name'
UsersSamAccountName = 'SamAccountName'
UsersCompany = 'Company'
Usersc = 'Country'
Manager = 'Manager'
# etc.
}
$otherMap = #{
UsersGIDNumber = 'GIDNumber'
UsersLoginShell = 'LoginShell'
UsersmsSFU30Name = 'MsSFU30Name'
UsersmsSFU30NisDomain = 'MsSFU30NisDomain'
# etc.
}
Next, import the CSV and loop over each entry:
$csv = Import-Csv -Path 'X:\your_importFile.csv'
foreach ($item in $csv) {
# two empty Hashtables for splatting
$NewUserAttr = #{}
$OtherAttr = #{}
# pre fill the default attributes you need for all users
$NewUserAttr['Enabled'] = $UsersEnabled
$NewUserAttr['Server'] = $TargetDomain
$NewUserAttr['Path'] = $ParentOU
# loop over the properties for each item in the CSV and only store
# the ones that are not empty and for which you can find a mapping
$item.PsObject.Properties | ForEach-Object {
if (![string]::IsNullOrWhiteSpace($_.Value)) {
if ($attribMap.Contains($_.Name)) { $NewUserAttr[$attribMap[$_.Name]] = $_.Value }
elseif ($otherMap.Contains($_.Name)) { $OtherAttr[$otherMap[$_.Name]] = $_.Value }
}
}
# join the hashtables together if we have OtherAttributes
if ($OtherAttr.Count) { $NewUserAttr['OtherAttributes'] = $OtherAttr }
# now try and create the new user
try {
New-ADUser #NewUserAttr -ErrorAction Stop
}
catch {
Write-Warning "Error creating user $($NewUserAttr.Name): $($_.Exception.Message)"
}
}
I run into this often with named parameters and null values. To avoid null values for your named parameters which $null indicates the parameter should be omitted rather than provided, follow these rules when creating the splat-hashtable:
When defining the hashtable, define any properties that will not (or should not be) $null, like so:
$splatArgs = #{
ParameterOne = 'Value'
ParameterTwo = 'OtherValue'
}
For parameters that may or may not need to be provided based on whether its value is $null (or evaluates falsey), conditionally add it to the hashtable:
if( $someVar ) {
$splatArgs.ConditionalParameter = $someVar
}
Repeat 2. for each conditional argument you have. Alternatively, you could initialize the hashtable with all possible parameter names and values, then strip them out after after checking falsiness, comparing to $null, etc.
# You could do any condition here but in this example,
# We will do a simple falsiness test on each key's value
$removeParameterNames = $splatArgs.Keys | Where-Object {
!( $splatArgs.$_ )
}
# Use another loop here since we don't want to modify the hashtable
# while iterating over its keys
foreach( $key in $removeParameterNames ) {
$splatArgs.Remove($key)
}
Here is what I ended up getting to work, which is very similar to what Bender recommended and what Richard from the link in my above comments recommended.
$NewUserAttr = #{
'Name' = $ADUser.UsersName
'SamAccountName' = $ADUser.UsersSamAccountName
'AccountPassword' = (ConvertTo-SecureString -AsPlainText "<Ab#1Cd!2>" -Force)
'Company' = $ADUser.UsersCompany
'Department' = $ADUser.UsersDepartment
'DisplayName' = $ADUser.UsersDisplayName
'EmailAddress' = $ADUser.UsersMail
'EmployeeID' = $ADUser.UsersEmployeeID
'Enabled' = $UsersEnabled
'GivenName' = $ADUser.UsersGivenName
'MobilePhone' = $ADUser.UsersMobileNum
'OfficePhone' = $ADUser.UsersTelephoneNumber
'SurName' = $ADUser.UsersSN
'Title' = $ADUser.UsersTitle
'userPrincipalname' = $ADUser.UsersUPN
'Path' = $ParentOU
'Server' = $TargetDomain
'OtherAttr' = #{
'GIDNumber' = $ADUser.UsersGIDNumber
'LoginShell' = $ADUser.UsersLoginShell
'msSFU30Name' = $ADUser.UsersMsSFU30Name
'msSFU30NisDomain' = $ADUser.UsersMsSFU30NisDomain
'Uid' = $ADUser.UsersUid
'uidNumber' = $ADUser.UsersUidNum
'unixHomeDirectory' = $ADUser.UsersUHD
}
}
# Check the uncommon attributes and add them to the hash table if not null
if($ADUser.Usersl){$NewUserAttr.add('City',$ADUser.Usersl)}
if($ADUser.Usersc){$NewUserAttr.add('Country',$ADUser.Usersc)}
if($ADUser.UsersInitials){$NewUserAttr.add('Initials',$ADUser.UsersInitials)}
if($ADUser.Manager){$NewUserAttr.add('Manager',$ADUser.Manager)}
if($ADUser.UsersPostalCode){$NewUserAttr.add('PostalCode',$ADUser.UsersPostalCode)}
if($ADUser.UsersST){$NewUserAttr.add('State',$ADUser.UsersST)}
if($ADUser.UsersStreetAddress){$NewUserAttr.add('StreetAddress',$ADUser.UsersStreetAddress)}
if($ADUser.physicaldeliveryofficename){$NewUserAttr.OtherAttr.add('physicaldeliveryofficename',$ADUser.physicaldeliveryofficename)}
if($ADUser.UsersSSN){$NewUserAttr.OtherAttr.add('SSN',$ADUser.UsersSSN)}
#Add new user to destination domain
try {
$UserExists = Get-ADUser -Identity $ADUser.UsersName -Server $TargetDomain
if ($UserExists) {
"Exists,$($ADUser.UsersName),$ParentOU`n" | Out-File $UserCreationLog -Append -Force
} else {
New-ADUser #NewUserAttr -ErrorAction Continue
#Change password
"Added,$($ADUser.UsersSamAccountName),$($ADUser.UsersName),$ParentOU`n" | Out-File $UserCreationLog -Append -Force
}
} catch {
$ErrorMsg = $_.Exception.Message
Write-Log -Message "Unable to create user, $($ADUser.UsersName): $ErrorMsg." -Severity Error -LogPath $LogFile
Write-Log -Message "Failed users attributes: $NewUserAttr $($NewUserAttr.OtherAttr)" -Severity Error -LogPath $LogPath
}
Now I just need to test each of these suggest answers to see which is the fastest! Thanks everyone!!

PowerShell - PSObject properties becoming sets with the name of the next property appended to the collection

I am trying to create a simple object in PowerShell that has four values. A boolean, two strings, and an integer. The problem is that if I call this function:
function New-ResultObject($Status, $Function, $Message, $Priority)
{
return New-Object psobject -property #{
Status = $Status; `
Function = $Function; `
Message = $Message; `
Priority = $Priority
}
}
Called this way:
$object= New-ResultObject -Status $false,`
-Function "FunctionName",`
-Message "This is the message",`
-Priority 2
Then do a $object | select * I get this as a result:
$object.Function = {FunctionName, -Status}
$object.Status = {False, -Function}
$object.Priority = 2
$object.Message = {This is the message, -Priority}
Instead of what I expected to be:
$object.Function = FunctionName
$object.Status = False
$object.Priority = 2
$object.Message = This is the message
I have tried doing an Add-Member approach with each property on its' own line, but it still results in the same object, where the property name is added as a second value to the initial property. What am I missing?
It's a common pitfall. You define the parameters as a comma delimited list but you call them space delimited.
$object= New-ResultObject -Status $false -Function "FunctionName" -Message "This is the message" -Priority 2
Also, I must implore you to stop using the backtick newline escaping trick. Instead you can use splatting.
$params = #{
Status = $false
Function = "FunctionName"
Message = "This is the message"
Priority = 2
}
$object = New-ResultObject #params
Thank you Doug Maurer. If you want to post an answer I can accept it. Otherwise, this was the issue, commas are not needed for multiple parameters and were causing the parameter tags to be passed in as multiple parts to the loosely-typed function parameters.
Here is a working example:
$object= New-ResultObject -Status $false `
-Function "FunctionName" `
-Message "This is the message" `
-Priority 2

Student Script for creating new users error :System.String' to the type 'System.Management.Automation.SwitchParameter

I'm pulling some user info from a .csv to create new users,
I've splatted the New User Params at the suggestion of someone here
but I'm getting this error
New-ADUser : Cannot convert 'System.String' to the type 'System.Management.Automation.SwitchParameter' required by parameter
'Confirm'.
At C:\Users\Administrator\Documents\GitHub\cyclone-internal-user-sync-1\Bamboo Attributes form a csv.ps1:68 char:28
+ New-ADUser #NewUserParms
+ ~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-ADUser], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.ActiveDirectory.Management.Commands.NewADUser
I have no idea what this is haha, I've tried adding an erroraction stop to the new-aduser but that didn't have any effect
I have added trims and a section to remove spaces from usernames. to deal with multipart names such as Van der.... etc
#Bamboo Attributes from a .csv
#Enter a path to your import CSV file
$ADUsers = Import-csv 'path'
#Bamboo Attributes from a .csv
#Enter a path to your import CSV file
$ADUsers = Import-csv 'C:\Users\Administrator\Documents\GitHub\cyclone-internal-user-sync-1\documentation\SampleUserAttributes.csv'
#$apiRequest = Get-Content -Raw -Path C:\Users\alexh\Documents\GitHub\cyclone-internal-user-sync-1\cyclone-internal-user-sync-1\fake-api-query.json | ConvertFrom-Json
foreach ($User in $ADUsers) {
$firstName = $user.FirstName.Trim()
$surname = $user.Surname.Trim()
$vaildUsernameFormat = "[^a-zA-Z_.]" # identifies anything that's _not_ a-z or underscore or .
$username = "($firstName'.'$surname)" -replace $vaildUsernameFormat, '' #removes anything that isn't a-z
$DefaultPassword = 'Pa$$w0rd'
$NewUserParms = #{
'samAccountName' = $username;
'Name' = "$firstname $surname";
'DisplayName' = "$firstname $surname";
'UserPrincipalName' = "$username#domain.com";
'GivenName' = $firstname;
'Surname' = $surname;
'EmailAddress' = $User.Email;
'AccountPassword' = (ConvertTo-SecureString $DefaultPassword -AsPlainText -Force);
'Enabled' = $true;
'Path' = "OU=Users,DC=domain,DC=com";
'co' = $User.Country;
'company' = $User.CompanyName;
'countryCode' = $user.countryCode;
'department' = $user.OrgDepartmentName;
'Employeeid' = $user.EmployeeId;
'exstentionAttribute1' = $user.ExstentionNumber;
'ipPhone' = $user.ExstentionNumber;
'L' = $user.location;
'mail' = $user.Email;
'mobile' = $user.Mobile;
'Manager' = $user.Manager;
'physicalDeliveryOffice' = $user.Branch;
'postalCode' = $user.PostalCode;
'postOfficeBox' = $user.PostOfficeBox;
'proxyAddresses' = $user.ProxyEmail;
'scriptPath' = $user.scriptPath;
'st' = $user.StreetName;
'Title' = $user.Title
}
write-host "$username this is username value"
#Check if the user account already exists in AD
if (Get-ADUser -F {
sAMAccountName -eq $username
}) {
#If user does exist, output a warning message
Write-Warning "A user account $username has already exist in Active Directory."
}
else {
#If a user does not exist then create a new user account
New-ADUser #NewUserParms
}
}
I've removed some of the user attributes just to make this a bit smaller.
here is the.csv as well in case I've messed something up there
link to .csv file on git
A little known fact about PowerShell is that you don't need to use the whole parameter name. You can use the partial name and as long as it matches only one parameter name, that's what PowerShell assumes you mean.
The one it's choking on is this:
'co' = $User.Country;
If you look at the documentation for New-ADUser, it does not have a parameter called co. So PowerShell assumes it's a partial match to a known parameter, and the closest match is -Confirm. And the value in $User.Country doesn't make any sense for the -Confirm parameter, so it throws the error.
You will have to use the -OtherAttributes parameter to set all the other attributes that New-ADUser doesn't have a dedicated parameter for:
$NewUserParms = #{
...
'OtherAttributes = # {
'co' = $User.Country;
'exstentionAttribute1' = $user.ExstentionNumber;
...
}
...
}
As commented in this and previous questions, you are using New-ADUser $NewUserParms, where it should be New-ADUser #NewUserParms.
Also, to catch errors (you did add -ErrorAction Stop), you need to put that inside a try{..} catch{..} block.
I would also change the syntax you use for the -Filter parameter. Instead of using a scriptblock syntax {something -eq someotherthing}, you should create a string like "something -eq 'someotherthing'"
Try:
# define some 'constants'
$csvFile = 'X:\Folder\NewUsers.csv' # Enter a path to your import CSV file
$invalidCharacters = '[^a-z_.]' # identifies anything that's _not_ a-z or underscore or .
$DefaultPassword = 'Pa$$w0rd'
$securePassword = ConvertTo-SecureString -String $DefaultPassword -AsPlainText -Force
# read the input csv and loop through
Import-Csv -Path $csvFile | ForEach-Object {
$firstName = $_.FirstName.Trim()
$surname = $_.Surname.Trim()
$username = ('{0}.{1}' -f $firstName, $surname) -replace $invalidCharacters
# test if a user with that name already exists
$user = Get-ADUser -Filter "SamAccountName -eq '$username'" -ErrorAction SilentlyContinue
if ($user) {
Write-Warning "A user account $username already exist in Active Directory."
}
else {
Write-Host "Creating user $username"
$NewUserParms = #{
'SamAccountName' = $username
'Name' = "$firstname $surname"
'DisplayName' = "$firstname $surname"
'UserPrincipalName' = "$username#domain.com"
'GivenName' = $firstname
'Surname' = $surname
'EmailAddress' = $_.Email
'AccountPassword' = $securePassword
'Enabled' = $true
'Path' = "OU=Users,DC=domain,DC=com"
# add other properties to set from the CSV here.
# make sure you get the parameter data types correct and always check here:
# https://learn.microsoft.com/en-us/powershell/module/addsadministration/new-aduser?view=win10-ps#parameters
# switch parameters for the cmdlet can also be entered with a value $false or $true
}
try {
# '-ErrorAction Stop' ensures that also non-terminating errors get handled in the catch block
New-ADUser #NewUserParms -ErrorAction Stop
}
catch {
# something bad happened. Change 'Write-Warning' into 'throw' if you want your script to exit here
# inside a catch block, the '$_' automatic variable represents the actual exception object.
Write-Warning "Could not create account $username. $($_.Exception.Message)"
}
}
}

Splatting Arguments directly (w/o saving them in a temp variable)

I got a hashtable with connection-data for various (S)FTP-Connections. Thanks to the Module "WinSCP" I can easily create sessions for (S)FTP-Transfers
Currently I'm saving the Hastable-result in a temporary variable $arguments and use this variable for the splatting afterwards
Even if it's just one line of code that could be spared: is there a way to avoid the variable $arguments?
Sample-Hashtable:
$preferences = #{
"FTPUser" = #{
HostName = "ftp.domain.com"
PortNumber = 21
Protocol = "FTP"
}
"SFTPUser" = #{
HostName = "sftp.otherdomain.com"
GiveUpSecurityAndAcceptAnySshHostKey = $true
}
}
Function Get-FtpSession with the current temp-variable:
function Get-FtpSession ($user) {
$arguments = $preferences["$user"]
$session = New-WinSCPSession #arguments
return $session
}
I thought that I can use something like this (which does not work):
$session = New-WinSCPSession #($preferences["$user"])
P.S: I know that this question is kind of pointless but I was still wondering if it could be solved
If you are willing to modify that cmdlet, you can achieve something similar by adding ValueFromPipelineByPropertyName switch to it's parameters. Thus you can feed parameters from pipe.
function foo { [CmdletBinding()]param(
[parameter(ValueFromPipelineByPropertyName=$true)][String]$arg1,
[parameter(ValueFromPipelineByPropertyName=$true)][String]$arg2
)
Write-Host ($arg1 + " " + $arg2 + "!")
}
[pscustomobject]#{arg1="hello"; arg2="there"} | foo

Add proxyAddresses to Active Directory users when created in PowerShell

I am trying to add users in Active Directory. Those users need to have proxyAddresses. My problem is that those proxyAddresses are multiples and stored in an array.
I try :
$proxyAddresses = #("address1#test.com", "address2#test.com", "address3#test.com")
$userInstance = new-object Microsoft.ActiveDirectory.Management.ADUser
$userInstance.ProxyAddresses = $proxyAddresses
New-ADUser test -Instance $userInstance
And I get this error :
Invalid type 'System.Management.Automation.PSObject'. Parameter name: proxyAddresses
I would like to add this proxyAddresses array to the attribute proxyAddresses of my AD user but it don't seem to be possible.
Any idea how this could be done?
Anything wrong with using Set-ADUser?
$username = '...'
$proxyAddresses = 'address1#example.com', 'address2#example.com', 'address3#example.com'
New-ADUser -Name $username
Set-ADUser -Identity $username -Add #{
'proxyAddresses' = $proxyAddresses | % { "smtp:$_" }
}
I just had this same issue and I was pretty sure I was passing in a string array (that's how it was declared).
Problem was just before I sent my string array into AD I was passing it to "Sort-Object -Unique" - which unbeknownst to me was changing either the type or something that made the cmdlet unhappy.
Just FYI...Sort-Object can burn you in these circumstances.
So, in my testing of this. I made Get-ProxyAddresses at https://gist.github.com/PsychoData/dd475c27f7db5ce982cd6160c74ee1d0
function Get-ProxyAddresses
{
Param(
[Parameter(Mandatory=$true)]
[string[]]$username,
[string[]]$domains = 'domain.com'
)
#Strip off any leading # signs people may have provided. We'll add these later
$domains = $domains.Replace('#','')
$ProxyAddresses = New-Object System.Collections.ArrayList
foreach ($uname in $username) {
foreach ($domain in $domains ) {
if ($ProxyAddresses.Count -lt 1) {
$ProxyAddresses.Add( "SMTP:$uname#$domain" ) | Out-Null
} else {
$ProxyAddresses.Add( "smtp:$uname#$domain" ) | Out-Null
}
}
}
return $ProxyAddresses
}
It just returns as a collection. Pretty kludgy, but works for what I need. It also assumes the first username and first domain are the "primary"
I combined that with #ansgar's answer and tried just -OtherAttributes on New-Aduser
$proxyAddresses = Get-ProxyAddress -username 'john.smith', 'james.smith' -domains 'domain.com','domain.net'
New-ADUser -Name $username
-OtherAttributes #{
'proxyAddresses'= $proxyAddresses
}
Works perfectly and added the proxyAddresses for me right at creation, without having to have a separate set action afterwards.
If you are Going to do separate actions, I would recommend to use -Server, like below, so that you don't run into talking to two different DCs by accident (and you also know that the New-ADUser is finished and already there, you don't have to wait for replication)
#I like making it all in one command, above, but this should work fine too.
$ADServer = (Get-ADDomainController).name
New-ADUser -Server $ADServer -name $Username
Set-ADUSer -Server $ADServer -Identity $username -Add #{
'proxyAddresses' = $proxyAddresses | % { "smtp:$_" }
}