DSCResources/MSFT_xADUser/MSFT_xADUser.psm1
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "")] param () # Localized messages data LocalizedData { # culture="en-US" ConvertFrom-StringData @' RoleNotFoundError = Please ensure that the PowerShell module for role '{0}' is installed. RetrievingADUserError = Error looking up Active Directory user '{0}' ({0}@{1}). PasswordParameterConflictError = Parameter '{0}' cannot be set to '{1}' when the '{2}' parameter is specified. RetrievingADUser = Retrieving Active Directory user '{0}' ({0}@{1}) ... CreatingADDomainConnection = Creating connection to Active Directory domain '{0}' ... CheckingADUserPassword = Checking Active Directory user '{0}' password ... ADUserIsPresent = Active Directory user '{0}' ({0}@{1}) is present. ADUserNotPresent = Active Directory user '{0}' ({0}@{1}) was NOT present. ADUserNotDesiredPropertyState = User '{0}' property is NOT in the desired state. Expected '{1}', actual '{2}'. AddingADUser = Adding Active Directory user '{0}'. RemovingADUser = Removing Active Directory user '{0}'. UpdatingADUser = Updating Active Directory user '{0}'. SettingADUserPassword = Setting Active Directory user password. UpdatingADUserProperty = Updating user property '{0}' with/to '{1}'. RemovingADUserProperty = Removing user property '{0}' with '{1}'. MovingADUser = Moving user from '{0}' to '{1}'. RenamingADUser = Renaming user from '{0}' to '{1}'. '@ } ## Create a property map that maps the DSC resource parameters to the ## Active Directory user attributes. $adPropertyMap = @( @{ Parameter = 'CommonName'; ADProperty = 'cn'; } @{ Parameter = 'UserPrincipalName'; } @{ Parameter = 'DisplayName'; } @{ Parameter = 'Path'; ADProperty = 'distinguishedName'; } @{ Parameter = 'GivenName'; } @{ Parameter = 'Initials'; } @{ Parameter = 'Surname'; ADProperty = 'sn'; } @{ Parameter = 'Description'; } @{ Parameter = 'StreetAddress'; } @{ Parameter = 'POBox'; } @{ Parameter = 'City'; ADProperty = 'l'; } @{ Parameter = 'State'; ADProperty = 'st'; } @{ Parameter = 'PostalCode'; } @{ Parameter = 'Country'; ADProperty = 'c'; } @{ Parameter = 'Department'; } @{ Parameter = 'Division'; } @{ Parameter = 'Company'; } @{ Parameter = 'Office'; ADProperty = 'physicalDeliveryOfficeName'; } @{ Parameter = 'JobTitle'; ADProperty = 'title'; } @{ Parameter = 'EmailAddress'; ADProperty = 'mail'; } @{ Parameter = 'EmployeeID'; } @{ Parameter = 'EmployeeNumber'; } @{ Parameter = 'HomeDirectory'; } @{ Parameter = 'HomeDrive'; } @{ Parameter = 'HomePage'; ADProperty = 'wWWHomePage'; } @{ Parameter = 'ProfilePath'; } @{ Parameter = 'LogonScript'; ADProperty = 'scriptPath'; } @{ Parameter = 'Notes'; ADProperty = 'info'; } @{ Parameter = 'OfficePhone'; ADProperty = 'telephoneNumber'; } @{ Parameter = 'MobilePhone'; ADProperty = 'mobile'; } @{ Parameter = 'Fax'; ADProperty = 'facsimileTelephoneNumber'; } @{ Parameter = 'Pager'; } @{ Parameter = 'IPPhone'; } @{ Parameter = 'HomePhone'; } @{ Parameter = 'Enabled'; } @{ Parameter = 'Manager'; } @{ Parameter = 'PasswordNeverExpires'; UseCmdletParameter = $true; } @{ Parameter = 'CannotChangePassword'; UseCmdletParameter = $true; } ) function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')] param ( ## Only used if password is managed. [Parameter(Mandatory)] [System.String] $DomainName, # SamAccountName [Parameter(Mandatory)] [System.String] $UserName, [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Password, [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', # Common name (CN) [ValidateNotNull()] [System.String] $CommonName = $UserName, [ValidateNotNull()] [System.String] $UserPrincipalName, [ValidateNotNull()] [System.String] $DisplayName, [ValidateNotNull()] [System.String] $Path, [ValidateNotNull()] [System.String] $GivenName, [ValidateNotNull()] [System.String] $Initials, [ValidateNotNull()] [System.String] $Surname, [ValidateNotNull()] [System.String] $Description, [ValidateNotNull()] [System.String] $StreetAddress, [ValidateNotNull()] [System.String] $POBox, [ValidateNotNull()] [System.String] $City, [ValidateNotNull()] [System.String] $State, [ValidateNotNull()] [System.String] $PostalCode, [ValidateNotNull()] [System.String] $Country, [ValidateNotNull()] [System.String] $Department, [ValidateNotNull()] [System.String] $Division, [ValidateNotNull()] [System.String] $Company, [ValidateNotNull()] [System.String] $Office, [ValidateNotNull()] [System.String] $JobTitle, [ValidateNotNull()] [System.String] $EmailAddress, [ValidateNotNull()] [System.String] $EmployeeID, [ValidateNotNull()] [System.String] $EmployeeNumber, [ValidateNotNull()] [System.String] $HomeDirectory, [ValidateNotNull()] [System.String] $HomeDrive, [ValidateNotNull()] [System.String] $HomePage, [ValidateNotNull()] [System.String] $ProfilePath, [ValidateNotNull()] [System.String] $LogonScript, [ValidateNotNull()] [System.String] $Notes, [ValidateNotNull()] [System.String] $OfficePhone, [ValidateNotNull()] [System.String] $MobilePhone, [ValidateNotNull()] [System.String] $Fax, [ValidateNotNull()] [System.String] $HomePhone, [ValidateNotNull()] [System.String] $Pager, [ValidateNotNull()] [System.String] $IPPhone, ## User's manager specified as a Distinguished Name (DN) [ValidateNotNull()] [System.String] $Manager, [ValidateNotNull()] [System.Boolean] $Enabled = $true, [ValidateNotNull()] [System.Boolean] $CannotChangePassword, [ValidateNotNull()] [System.Boolean] $PasswordNeverExpires, [ValidateNotNull()] [System.String] $DomainController, ## Ideally this should just be called 'Credential' but is here for backwards compatibility [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $DomainAdministratorCredential ) Assert-Module -ModuleName 'ActiveDirectory'; try { $adCommonParameters = Get-ADCommonParameters @PSBoundParameters; $adProperties = @(); ## Create an array of the AD propertie names to retrieve from the property map foreach ($property in $adPropertyMap) { if ($property.ADProperty) { $adProperties += $property.ADProperty; } else { $adProperties += $property.Parameter; } } Write-Verbose -Message ($LocalizedData.RetrievingADUser -f $UserName, $DomainName); $adUser = Get-ADUser @adCommonParameters -Properties $adProperties; Write-Verbose -Message ($LocalizedData.ADUserIsPresent -f $UserName, $DomainName); $Ensure = 'Present'; } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { Write-Verbose -Message ($LocalizedData.ADUserNotPresent -f $UserName, $DomainName); $Ensure = 'Absent'; } catch { Write-Error -Message ($LocalizedData.RetrievingADUserError -f $UserName, $DomainName); throw $_; } $targetResource = @{ DomainName = $DomainName; Password = $Password; UserName = $UserName; DistinguishedName = $adUser.DistinguishedName; ## Read-only property Ensure = $Ensure; DomainController = $DomainController; } ## Retrieve each property from the ADPropertyMap and add to the hashtable foreach ($property in $adPropertyMap) { if ($property.Parameter -eq 'Path') { ## The path returned is not the parent container if (-not [System.String]::IsNullOrEmpty($adUser.DistinguishedName)) { $targetResource['Path'] = Get-ADObjectParentDN -DN $adUser.DistinguishedName; } } elseif ($property.ADProperty) { ## The AD property name is different to the function parameter to use this $targetResource[$property.Parameter] = $adUser.($property.ADProperty); } else { ## The AD property name matches the function parameter $targetResource[$property.Parameter] = $adUser.($property.Parameter); } } return $targetResource; } #end function Get-TargetResource function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')] param ( ## Only used if password is managed. [Parameter(Mandatory)] [System.String] $DomainName, # SamAccountName [Parameter(Mandatory)] [System.String] $UserName, [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Password, [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', # Common name (CN) [ValidateNotNull()] [System.String] $CommonName = $UserName, [ValidateNotNull()] [System.String] $UserPrincipalName, [ValidateNotNull()] [System.String] $DisplayName, [ValidateNotNull()] [System.String] $Path, [ValidateNotNull()] [System.String] $GivenName, [ValidateNotNull()] [System.String] $Initials, [ValidateNotNull()] [System.String] $Surname, [ValidateNotNull()] [System.String] $Description, [ValidateNotNull()] [System.String] $StreetAddress, [ValidateNotNull()] [System.String] $POBox, [ValidateNotNull()] [System.String] $City, [ValidateNotNull()] [System.String] $State, [ValidateNotNull()] [System.String] $PostalCode, [ValidateNotNull()] [System.String] $Country, [ValidateNotNull()] [System.String] $Department, [ValidateNotNull()] [System.String] $Division, [ValidateNotNull()] [System.String] $Company, [ValidateNotNull()] [System.String] $Office, [ValidateNotNull()] [System.String] $JobTitle, [ValidateNotNull()] [System.String] $EmailAddress, [ValidateNotNull()] [System.String] $EmployeeID, [ValidateNotNull()] [System.String] $EmployeeNumber, [ValidateNotNull()] [System.String] $HomeDirectory, [ValidateNotNull()] [System.String] $HomeDrive, [ValidateNotNull()] [System.String] $HomePage, [ValidateNotNull()] [System.String] $ProfilePath, [ValidateNotNull()] [System.String] $LogonScript, [ValidateNotNull()] [System.String] $Notes, [ValidateNotNull()] [System.String] $OfficePhone, [ValidateNotNull()] [System.String] $MobilePhone, [ValidateNotNull()] [System.String] $Fax, [ValidateNotNull()] [System.String] $HomePhone, [ValidateNotNull()] [System.String] $Pager, [ValidateNotNull()] [System.String] $IPPhone, ## User's manager specified as a Distinguished Name (DN) [ValidateNotNull()] [System.String] $Manager, [ValidateNotNull()] [System.Boolean] $Enabled = $true, [ValidateNotNull()] [System.Boolean] $CannotChangePassword, [ValidateNotNull()] [System.Boolean] $PasswordNeverExpires, [ValidateNotNull()] [System.String] $DomainController, [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $DomainAdministratorCredential ) Assert-Parameters @PSBoundParameters; $targetResource = Get-TargetResource @PSBoundParameters; $isCompliant = $true; if ($Ensure -eq 'Absent') { if ($targetResource.Ensure -eq 'Present') { Write-Verbose -Message ($LocalizedData.ADUserNotDesiredPropertyState -f 'Ensure', $PSBoundParameters.Ensure, $targetResource.Ensure); $isCompliant = $false; } } else { ## Add common name, ensure and enabled as they may not be explicitly passed and we want to enumerate them $PSBoundParameters['Ensure'] = $Ensure; $PSBoundParameters['Enabled'] = $Enabled; foreach ($parameter in $PSBoundParameters.Keys) { if ($parameter -eq 'Password') { $testPasswordParams = @{ Username = $UserName; Password = $Password; DomainName = $DomainName; } if ($DomainAdministratorCredential) { $testPasswordParams['DomainAdministratorCredential'] = $DomainAdministratorCredential; } if (-not (Test-Password @testPasswordParams)) { Write-Verbose -Message ($LocalizedData.ADUserNotDesiredPropertyState -f 'Password', '<Password>', '<Password>'); $isCompliant = $false; } } # Only check properties that are returned by Get-TargetResource elseif ($targetResource.ContainsKey($parameter)) { ## This check is required to be able to explicitly remove values with an empty string, if required if (([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter)) -and ([System.String]::IsNullOrEmpty($targetResource.$parameter))) { # Both values are null/empty and therefore we are compliant } elseif ($PSBoundParameters.$parameter -ne $targetResource.$parameter) { Write-Verbose -Message ($LocalizedData.ADUserNotDesiredPropertyState -f $parameter, $PSBoundParameters.$parameter, $targetResource.$parameter); $isCompliant = $false; } } } #end foreach PSBoundParameter } return $isCompliant; } #end function Test-TargetResource function Set-TargetResource { [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')] param ( ## Only used if password is managed. [Parameter(Mandatory)] [System.String] $DomainName, # SamAccountName [Parameter(Mandatory)] [System.String] $UserName, [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Password, [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', [ValidateNotNull()] [System.String] $CommonName = $UserName, [ValidateNotNull()] [System.String] $UserPrincipalName, [ValidateNotNull()] [System.String] $DisplayName, [ValidateNotNull()] [System.String] $Path, [ValidateNotNull()] [System.String] $GivenName, [ValidateNotNull()] [System.String] $Initials, [ValidateNotNull()] [System.String] $Surname, [ValidateNotNull()] [System.String] $Description, [ValidateNotNull()] [System.String] $StreetAddress, [ValidateNotNull()] [System.String] $POBox, [ValidateNotNull()] [System.String] $City, [ValidateNotNull()] [System.String] $State, [ValidateNotNull()] [System.String] $PostalCode, [ValidateNotNull()] [System.String] $Country, [ValidateNotNull()] [System.String] $Department, [ValidateNotNull()] [System.String] $Division, [ValidateNotNull()] [System.String] $Company, [ValidateNotNull()] [System.String] $Office, [ValidateNotNull()] [System.String] $JobTitle, [ValidateNotNull()] [System.String] $EmailAddress, [ValidateNotNull()] [System.String] $EmployeeID, [ValidateNotNull()] [System.String] $EmployeeNumber, [ValidateNotNull()] [System.String] $HomeDirectory, [ValidateNotNull()] [System.String] $HomeDrive, [ValidateNotNull()] [System.String] $HomePage, [ValidateNotNull()] [System.String] $ProfilePath, [ValidateNotNull()] [System.String] $LogonScript, [ValidateNotNull()] [System.String] $Notes, [ValidateNotNull()] [System.String] $OfficePhone, [ValidateNotNull()] [System.String] $MobilePhone, [ValidateNotNull()] [System.String] $Fax, [ValidateNotNull()] [System.String] $HomePhone, [ValidateNotNull()] [System.String] $Pager, [ValidateNotNull()] [System.String] $IPPhone, ## User's manager specified as a Distinguished Name (DN) [ValidateNotNull()] [System.String] $Manager, [ValidateNotNull()] [System.Boolean] $Enabled = $true, [ValidateNotNull()] [System.Boolean] $CannotChangePassword, [ValidateNotNull()] [System.Boolean] $PasswordNeverExpires, [ValidateNotNull()] [System.String] $DomainController, [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $DomainAdministratorCredential ) Assert-Parameters @PSBoundParameters; $targetResource = Get-TargetResource @PSBoundParameters; ## Add common name, ensure and enabled as they may not be explicitly passed $PSBoundParameters['Ensure'] = $Ensure; $PSBoundParameters['Enabled'] = $Enabled; if ($Ensure -eq 'Present') { if ($targetResource.Ensure -eq 'Absent') { ## User does not exist and needs creating $newADUserParams = Get-ADCommonParameters @PSBoundParameters -UseNameParameter; if ($PSBoundParameters.ContainsKey('Path')) { $newADUserParams['Path'] = $Path; } Write-Verbose -Message ($LocalizedData.AddingADUser -f $UserName); New-ADUser @newADUserParams -SamAccountName $UserName; ## Now retrieve the newly created user $targetResource = Get-TargetResource @PSBoundParameters; } $setADUserParams = Get-ADCommonParameters @PSBoundParameters; $replaceUserProperties = @{}; $removeUserProperties = @{}; foreach ($parameter in $PSBoundParameters.Keys) { ## Only check/action properties specified/declared parameters that match one of the function's ## parameters. This will ignore common parameters such as -Verbose etc. if ($targetResource.ContainsKey($parameter)) { if ($parameter -eq 'Path' -and ($PSBoundParameters.Path -ne $targetResource.Path)) { ## Cannot move users by updating the DistinguishedName property $adCommonParameters = Get-ADCommonParameters @PSBoundParameters; ## Using the SamAccountName for identity with Move-ADObject does not work, use the DN instead $adCommonParameters['Identity'] = $targetResource.DistinguishedName; Write-Verbose -Message ($LocalizedData.MovingADUser -f $targetResource.Path, $PSBoundParameters.Path); Move-ADObject @adCommonParameters -TargetPath $PSBoundParameters.Path; } elseif ($parameter -eq 'CommonName' -and ($PSBoundParameters.CommonName -ne $targetResource.CommonName)) { ## Cannot rename users by updating the CN property directly $adCommonParameters = Get-ADCommonParameters @PSBoundParameters; ## Using the SamAccountName for identity with Rename-ADObject does not work, use the DN instead $adCommonParameters['Identity'] = $targetResource.DistinguishedName; Write-Verbose -Message ($LocalizedData.RenamingADUser -f $targetResource.CommonName, $PSBoundParameters.CommonName); Rename-ADObject @adCommonParameters -NewName $PSBoundParameters.CommonName; } elseif ($parameter -eq 'Password') { $adCommonParameters = Get-ADCommonParameters @PSBoundParameters; Write-Verbose -Message ($LocalizedData.SettingADUserPassword -f $UserName); Set-ADAccountPassword @adCommonParameters -Reset -NewPassword $Password.Password; } elseif ($parameter -eq 'Enabled' -and ($PSBoundParameters.$parameter -ne $targetResource.$parameter)) { ## We cannot enable/disable an account with -Add or -Replace parameters, but inform that ## we will change this as it is out of compliance (it always gets set anyway) Write-Verbose -Message ($LocalizedData.UpdatingADUserProperty -f $parameter, $PSBoundParameters.$parameter); } elseif ($PSBoundParameters.$parameter -ne $targetResource.$parameter) { ## Find the associated AD property $adProperty = $adPropertyMap | Where-Object { $_.Parameter -eq $parameter }; if ([System.String]::IsNullOrEmpty($adProperty)) { ## We can't do anything is an empty AD property! } elseif ([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter)) { ## We are removing properties ## Only remove if the existing value in not null or empty if (-not ([System.String]::IsNullOrEmpty($targetResource.$parameter))) { Write-Verbose -Message ($LocalizedData.RemovingADUserProperty -f $parameter, $PSBoundParameters.$parameter); if ($adProperty.UseCmdletParameter -eq $true) { ## We need to pass the parameter explicitly to Set-ADUser, not via -Remove $setADUserParams[$adProperty.Parameter] = $PSBoundParameters.$parameter; } elseif ([System.String]::IsNullOrEmpty($adProperty.ADProperty)) { $removeUserProperties[$adProperty.Parameter] = $targetResource.$parameter; } else { $removeUserProperties[$adProperty.ADProperty] = $targetResource.$parameter; } } } #end if remove existing value else { ## We are replacing the existing value Write-Verbose -Message ($LocalizedData.UpdatingADUserProperty -f $parameter, $PSBoundParameters.$parameter); if ($adProperty.UseCmdletParameter -eq $true) { ## We need to pass the parameter explicitly to Set-ADUser, not via -Replace $setADUserParams[$adProperty.Parameter] = $PSBoundParameters.$parameter; } elseif ([System.String]::IsNullOrEmpty($adProperty.ADProperty)) { $replaceUserProperties[$adProperty.Parameter] = $PSBoundParameters.$parameter; } else { $replaceUserProperties[$adProperty.ADProperty] = $PSBoundParameters.$parameter; } } #end if replace existing value } } #end if TargetResource parameter } #end foreach PSBoundParameter ## Only pass -Remove and/or -Replace if we have something to set/change if ($replaceUserProperties.Count -gt 0) { $setADUserParams['Replace'] = $replaceUserProperties; } if ($removeUserProperties.Count -gt 0) { $setADUserParams['Remove'] = $removeUserProperties; } Write-Verbose -Message ($LocalizedData.UpdatingADUser -f $UserName); [ref] $null = Set-ADUser @setADUserParams -Enabled $Enabled; } elseif (($Ensure -eq 'Absent') -and ($targetResource.Ensure -eq 'Present')) { ## User exists and needs removing Write-Verbose ($LocalizedData.RemovingADUser -f $UserName); $adCommonParameters = Get-ADCommonParameters @PSBoundParameters; [ref] $null = Remove-ADUser @adCommonParameters -Confirm:$false; } } #end function Set-TargetResource # Internal function to validate unsupported options/configurations function Assert-Parameters { [CmdletBinding()] param ( [ValidateNotNull()] [System.Management.Automation.PSCredential] $Password, [ValidateNotNull()] [System.Boolean] $Enabled = $true, [Parameter(ValueFromRemainingArguments)] $IgnoredArguments ) ## We cannot test/set passwords on disabled AD accounts if (($PSBoundParameters.ContainsKey('Password')) -and ($Enabled -eq $false)) { $throwInvalidArgumentErrorParams = @{ ErrorId = 'xADUser_DisabledAccountPasswordConflict'; ErrorMessage = $LocalizedData.PasswordParameterConflictError -f 'Enabled', $false, 'Password'; } ThrowInvalidArgumentError @throwInvalidArgumentErrorParams; } } #end function Assert-Parameters # Internal function to test the validity of a user's password. function Test-Password { [CmdletBinding()] param ( [Parameter(Mandatory)] [System.String] $DomainName, [Parameter(Mandatory)] [System.String] $UserName, [Parameter(Mandatory)] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Password, [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $DomainAdministratorCredential ) Write-Verbose -Message ($LocalizedData.CreatingADDomainConnection -f $DomainName); Add-Type -AssemblyName 'System.DirectoryServices.AccountManagement'; if ($DomainAdministratorCredential) { $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext( 'Domain', $DomainName, $DomainAdministratorCredential.UserName, ` $DomainAdministratorCredential.GetNetworkCredential().Password); } else { $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext('Domain', $DomainName, $null, $null); } Write-Verbose -Message ($LocalizedData.CheckingADUserPassword -f $UserName); return $principalContext.ValidateCredentials($UserName, $Password.GetNetworkCredential().Password); } #end function Test-Password ## Import the common AD functions $adCommonFunctions = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.ps1'; . $adCommonFunctions; Export-ModuleMember -Function *-TargetResource |