DSCResources/MSFT_ADComputer/MSFT_ADComputer.psm1
$resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent $modulesFolderPath = Join-Path -Path $resourceModulePath -ChildPath 'Modules' $aDCommonModulePath = Join-Path -Path $modulesFolderPath -ChildPath 'ActiveDirectoryDsc.Common' Import-Module -Name $aDCommonModulePath $dscResourceCommonModulePath = Join-Path -Path $modulesFolderPath -ChildPath 'DscResource.Common' Import-Module -Name $dscResourceCommonModulePath $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' <# A property map that maps the resource parameters to the corresponding Active Directory computer account object attribute. #> $script:computerObjectPropertyMap = @( @{ ParameterName = 'ComputerName' PropertyName = 'CN' }, @{ ParameterName = 'Location' }, @{ ParameterName = 'DnsHostName' }, @{ ParameterName = 'ServicePrincipalNames' PropertyName = 'ServicePrincipalName' }, @{ ParameterName = 'UserPrincipalName' }, @{ ParameterName = 'DisplayName' }, @{ ParameterName = 'Path' PropertyName = 'DistinguishedName' }, @{ ParameterName = 'Description' }, @{ ParameterName = 'Enabled' }, @{ ParameterName = 'Manager' PropertyName = 'ManagedBy' }, @{ ParameterName = 'DistinguishedName' ParameterType = 'Read' PropertyName = 'DistinguishedName' }, @{ ParameterName = 'SID' ParameterType = 'Read' } ) <# .SYNOPSIS Returns the current state of the Active Directory computer account. .PARAMETER ComputerName Specifies the name of the Active Directory computer account to manage. You can identify a computer by its distinguished name, GUID, security identifier (SID) or Security Accounts Manager (SAM) account name. .PARAMETER RequestFile Specifies the full path to the Offline Domain Join Request file to create. .PARAMETER EnabledOnCreation Specifies if the computer account is created enabled or disabled. By default the Enabled property of the computer account will be set to the default value of the cmdlet New-ADComputer. This property is ignored if the parameter RequestFile is specified in the same configuration. This parameter does not enforce the property `Enabled`. To enforce the property `Enabled` see the resource ADObjectEnabledState. .PARAMETER DomainController Specifies the Active Directory Domain Services instance to connect to perform the task. Used by Get-ADCommonParameters and is returned as a common parameter. .PARAMETER Credential Specifies the user account credentials to use to perform the task. Used by Get-ADCommonParameters and is returned as a common parameter. .PARAMETER RestoreFromRecycleBin Try to restore the organizational unit from the recycle bin before creating a new one. #> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] [System.String] $ComputerName, [Parameter()] [ValidateNotNull()] [System.String] $RequestFile, [Parameter()] [ValidateNotNull()] [System.String] $DomainController, [Parameter()] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential, [Parameter()] [ValidateNotNull()] [System.Boolean] $RestoreFromRecycleBin, [Parameter()] [ValidateNotNull()] [System.Boolean] $EnabledOnCreation ) Assert-Module -ModuleName 'ActiveDirectory' -ImportModule <# These are properties that have no corresponding property in a Computer account object. #> $getTargetResourceReturnValue = @{ Ensure = 'Absent' ComputerName = $null Location = $null DnsHostName = $null ServicePrincipalNames = $null UserPrincipalName = $null DisplayName = $null Path = $null Description = $null Enabled = $false Manager = $null DomainController = $DomainController Credential = $Credential RequestFile = $RequestFile RestoreFromRecycleBin = $RestoreFromRecycleBin EnabledOnCreation = $EnabledOnCreation DistinguishedName = $null SID = $null SamAccountName = $null } $getADComputerResult = $null try { <# Create an array of the Active Directory Computer object property names to retrieve from the Computer object. #> $computerObjectProperties = Convert-PropertyMapToObjectProperties -PropertyMap $script:computerObjectPropertyMap <# When the property ServicePrincipalName is read with Get-ADComputer the property name must be 'ServicePrincipalNames', but when it is written with Set-ADComputer the property name must be 'ServicePrincipalName'. This difference is handled here. #> $computerObjectProperties = @($computerObjectProperties | Where-Object -FilterScript { $_ -ne 'ServicePrincipalName' }) $computerObjectProperties += @('ServicePrincipalNames') Write-Verbose -Message ($script:localizedData.RetrievingComputerAccount -f $ComputerName) $getADComputerParameters = Get-ADCommonParameters @PSBoundParameters $getADComputerParameters['Properties'] = $computerObjectProperties # If the computer account is not found Get-ADComputer will throw an error. $getADComputerResult = Get-ADComputer @getADComputerParameters Write-Verbose -Message ($script:localizedData.ComputerAccountIsPresent -f $ComputerName) $getTargetResourceReturnValue['Ensure'] = 'Present' $getTargetResourceReturnValue['ComputerName'] = $getADComputerResult.CN $getTargetResourceReturnValue['Location'] = $getADComputerResult.Location $getTargetResourceReturnValue['DnsHostName'] = $getADComputerResult.DnsHostName $getTargetResourceReturnValue['ServicePrincipalNames'] = [System.String[]] $getADComputerResult.ServicePrincipalNames $getTargetResourceReturnValue['UserPrincipalName'] = $getADComputerResult.UserPrincipalName $getTargetResourceReturnValue['DisplayName'] = $getADComputerResult.DisplayName $getTargetResourceReturnValue['Path'] = Get-ADObjectParentDN -DN $getADComputerResult.DistinguishedName $getTargetResourceReturnValue['Description'] = $getADComputerResult.Description $getTargetResourceReturnValue['Enabled'] = $getADComputerResult.Enabled $getTargetResourceReturnValue['Manager'] = $getADComputerResult.ManagedBy $getTargetResourceReturnValue['DomainController'] = $DomainController $getTargetResourceReturnValue['Credential'] = $Credential $getTargetResourceReturnValue['RequestFile'] = $RequestFile $getTargetResourceReturnValue['RestoreFromRecycleBin'] = $RestoreFromRecycleBin $getTargetResourceReturnValue['EnabledOnCreation'] = $EnabledOnCreation $getTargetResourceReturnValue['DistinguishedName'] = $getADComputerResult.DistinguishedName $getTargetResourceReturnValue['SID'] = $getADComputerResult.SID $getTargetResourceReturnValue['SamAccountName'] = $getADComputerResult.SamAccountName } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { Write-Verbose -Message ($script:localizedData.ComputerAccountIsAbsent -f $ComputerName) } catch { $errorMessage = $script:localizedData.FailedToRetrieveComputerAccount -f $ComputerName New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } return $getTargetResourceReturnValue } <# .SYNOPSIS Determines if the Active Directory computer account is in the desired state. .PARAMETER ComputerName Specifies the name of the Active Directory computer account to manage. You can identify a computer by its distinguished name, GUID, security identifier (SID) or Security Accounts Manager (SAM) account name. .PARAMETER Ensure Specifies whether the computer account is present or absent. Valid values are 'Present' and 'Absent'. The default is 'Present'. .PARAMETER UserPrincipalName Specifies the UPN assigned to the computer account. .PARAMETER DisplayName Specifies the display name of the computer. .PARAMETER Path Specifies the X.500 path of the container where the computer is located. .PARAMETER Location Specifies the location of the computer, such as an office number. .PARAMETER DnsHostName Specifies the fully qualified domain name (FQDN) of the computer. .PARAMETER ServicePrincipalNames Specifies the service principal names for the computer account. .PARAMETER Description Specifies a description of the computer account. .PARAMETER Manager Specifies the user or group Distinguished Name that manages the computer account. Valid values are the user's or group's DistinguishedName, ObjectGUID, SID or SamAccountName. .PARAMETER RequestFile Specifies the full path to the Offline Domain Join Request file to create. .PARAMETER DomainController Specifies the Active Directory Domain Services instance to connect to perform the task. .PARAMETER Credential Specifies the user account credentials to use to perform the task. .PARAMETER RestoreFromRecycleBin Try to restore the organizational unit from the recycle bin before creating a new one. .PARAMETER EnabledOnCreation Specifies if the computer account is created enabled or disabled. By default the Enabled property of the computer account will be set to the default value of the cmdlet New-ADComputer. This property is ignored if the parameter RequestFile is specified in the same configuration. This parameter does not enforce the property `Enabled`. To enforce the property `Enabled` see the resource ADObjectEnabledState. #> function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( # Common Name [Parameter(Mandatory = $true)] [System.String] $ComputerName, [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', [Parameter()] [ValidateNotNull()] [System.String] $UserPrincipalName, [Parameter()] [ValidateNotNull()] [System.String] $DisplayName, [Parameter()] [ValidateNotNull()] [System.String] $Path, [Parameter()] [ValidateNotNull()] [System.String] $Location, [Parameter()] [ValidateNotNull()] [System.String] $DnsHostName, [Parameter()] [ValidateNotNull()] [System.String[]] $ServicePrincipalNames, [Parameter()] [ValidateNotNull()] [System.String] $Description, [Parameter()] [ValidateNotNull()] [System.String] $Manager, [Parameter()] [ValidateNotNull()] [System.String] $RequestFile, [Parameter()] [ValidateNotNull()] [System.String] $DomainController, [Parameter()] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential, [Parameter()] [ValidateNotNull()] [System.Boolean] $RestoreFromRecycleBin, [Parameter()] [ValidateNotNull()] [System.Boolean] $EnabledOnCreation ) Write-Verbose -Message ( $script:localizedData.TestConfiguration -f $ComputerName ) $getTargetResourceParameters = @{ ComputerName = $ComputerName RequestFile = $RequestFile DomainController = $DomainController Credential = $Credential RestoreFromRecycleBin = $RestoreFromRecycleBin EnabledOnCreation = $EnabledOnCreation } # Need the @() around this to get a new array to enumerate. @($getTargetResourceParameters.Keys) | ForEach-Object { if (-not $PSBoundParameters.ContainsKey($_)) { $getTargetResourceParameters.Remove($_) } } $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters $testTargetResourceReturnValue = $true if ($Ensure -eq 'Absent') { if ($getTargetResourceResult.Ensure -eq 'Present') { Write-Verbose -Message ( $script:localizedData.ComputerAccountShouldBeAbsent -f $ComputerName ) $testTargetResourceReturnValue = $false } } else { if ($getTargetResourceResult.Ensure -eq 'Absent') { Write-Verbose -Message ( $script:localizedData.ComputerAccountShouldBePresent -f $ComputerName ) $testTargetResourceReturnValue = $false } else { <# - Ignores the parameter ComputerName since we are not supporting renaming a computer account. - Ignore to compare the parameter ServicePrincipalNames here because it needs a special comparison, so it is handled afterwards. - Ignores the Enabled property because it is not enforced in this resource. #> $compareTargetResourceStateParameters = @{ CurrentValues = $getTargetResourceResult DesiredValues = $PSBoundParameters # This gives an array of properties to compare. Properties = $script:computerObjectPropertyMap.ParameterName # But these properties IgnoreProperties = @( 'ComputerName' 'ServicePrincipalNames' 'Enabled' ) } $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters if ($false -in $compareTargetResourceStateResult.InDesiredState) { $testTargetResourceReturnValue = $false } if ($PSBoundParameters.ContainsKey('ServicePrincipalNames')) { $testServicePrincipalNamesParameters = @{ ExistingServicePrincipalNames = $getTargetResourceResult.ServicePrincipalNames ServicePrincipalNames = $ServicePrincipalNames } $testTargetResourceReturnValue = Test-ServicePrincipalNames @testServicePrincipalNamesParameters } } } if ($testTargetResourceReturnValue) { Write-Verbose -Message ($script:localizedData.ComputerAccountInDesiredState -f $ComputerName) } else { Write-Verbose -Message ($script:localizedData.ComputerAccountNotInDesiredState -f $ComputerName) } return $testTargetResourceReturnValue } <# .SYNOPSIS Creates, removes or modifies the Active Directory computer account. .PARAMETER ComputerName Specifies the name of the Active Directory computer account to manage. You can identify a computer by its distinguished name, GUID, security identifier (SID) or Security Accounts Manager (SAM) account name. .PARAMETER Ensure Specifies whether the computer account is present or absent. Valid values are 'Present' and 'Absent'. The default is 'Present'. .PARAMETER UserPrincipalName Specifies the UPN assigned to the computer account. .PARAMETER DisplayName Specifies the display name of the computer. .PARAMETER Path Specifies the X.500 path of the container where the computer is located. .PARAMETER Location Specifies the location of the computer, such as an office number. .PARAMETER DnsHostName Specifies the fully qualified domain name (FQDN) of the computer. .PARAMETER ServicePrincipalNames Specifies the service principal names for the computer account. .PARAMETER Description Specifies a description of the computer account. .PARAMETER Manager Specifies the user or group Distinguished Name that manages the computer account. Valid values are the user's or group's DistinguishedName, ObjectGUID, SID or SamAccountName. .PARAMETER RequestFile Specifies the full path to the Offline Domain Join Request file to create. .PARAMETER DomainController Specifies the Active Directory Domain Services instance to connect to perform the task. .PARAMETER Credential Specifies the user account credentials to use to perform the task. .PARAMETER RestoreFromRecycleBin Try to restore the organizational unit from the recycle bin before creating a new one. .PARAMETER EnabledOnCreation Specifies if the computer account is created enabled or disabled. By default the Enabled property of the computer account will be set to the default value of the cmdlet New-ADComputer. This property is ignored if the parameter RequestFile is specified in the same configuration. This parameter does not enforce the property `Enabled`. To enforce the property `Enabled` see the resource ADObjectEnabledState. #> function Set-TargetResource { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $ComputerName, [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', [Parameter()] [ValidateNotNull()] [System.String] $UserPrincipalName, [Parameter()] [ValidateNotNull()] [System.String] $DisplayName, [Parameter()] [ValidateNotNull()] [System.String] $Path, [Parameter()] [ValidateNotNull()] [System.String] $Location, [Parameter()] [ValidateNotNull()] [System.String] $DnsHostName, [Parameter()] [ValidateNotNull()] [System.String[]] $ServicePrincipalNames, [Parameter()] [ValidateNotNull()] [System.String] $Description, [Parameter()] [ValidateNotNull()] [System.String] $Manager, [Parameter()] [ValidateNotNull()] [System.String] $RequestFile, [Parameter()] [ValidateNotNull()] [System.String] $DomainController, [Parameter()] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential, [Parameter()] [ValidateNotNull()] [System.Boolean] $RestoreFromRecycleBin, [Parameter()] [ValidateNotNull()] [System.Boolean] $EnabledOnCreation ) $getTargetResourceParameters = @{ ComputerName = $ComputerName RequestFile = $RequestFile DomainController = $DomainController Credential = $Credential RestoreFromRecycleBin = $RestoreFromRecycleBin EnabledOnCreation = $EnabledOnCreation } # Need the @() around this to get a new array to enumerate. @($getTargetResourceParameters.Keys) | ForEach-Object { if (-not $PSBoundParameters.ContainsKey($_)) { $getTargetResourceParameters.Remove($_) } } $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters if ($Ensure -eq 'Present') { if ($getTargetResourceResult.Ensure -eq 'Absent') { $restorationSuccessful = $false # Try to restore computer account from recycle bin if it exists. if ($RestoreFromRecycleBin) { Write-Verbose -Message ( $script:localizedData.RestoringComputerAccount -f $ComputerName ) $restoreADCommonObjectParameters = Get-ADCommonParameters @PSBoundParameters $restoreADCommonObjectParameters['ObjectClass'] = 'Computer' $restoreADCommonObjectParameters['ErrorAction'] = 'Stop' $restorationSuccessful = Restore-ADCommonObject @restoreADCommonObjectParameters } if (-not $RestoreFromRecycleBin -or ($RestoreFromRecycleBin -and -not $restorationSuccessful)) { <# The computer account does not exist, or the computer account was not present in recycle bin, so the computer account needs to be created. #> if ($RequestFile) { <# Use DJOIN to create the computer account as well as the Offline Domain Join (ODJ) request file. #> # This should only be performed on a Domain Member, so detect the Domain Name. $domainName = Get-DomainName Write-Verbose -Message ( $script:localizedData.CreateOfflineDomainJoinRequest -f $RequestFile, $ComputerName, $domainName ) $dJoinArguments = @( '/PROVISION' '/DOMAIN', $domainName '/MACHINE', $ComputerName ) if ($PSBoundParameters.ContainsKey('Path')) { $dJoinArguments += @( '/MACHINEOU', $Path ) } if ($PSBoundParameters.ContainsKey('DomainController')) { $dJoinArguments += @( '/DCNAME', $DomainController ) } $dJoinArguments += @( '/SAVEFILE', $RequestFile ) $startProcessParameters = @{ FilePath = 'djoin.exe' ArgumentList = $dJoinArguments Timeout = 300 } $dJoinProcessExitCode = Start-ProcessWithTimeout @startProcessParameters if ($dJoinProcessExitCode -ne 0) { $errorMessage = $script:localizedData.FailedToCreateOfflineDomainJoinRequest -f $ComputerName, $dJoinProcessExitCode New-InvalidOperationException -Message $errorMessage } else { Write-Verbose -Message ( $script:localizedData.CreatedOfflineDomainJoinRequestFile -f $RequestFile ) } } else { $newADComputerParameters = Get-ADCommonParameters @PSBoundParameters -UseNameParameter if ($PSBoundParameters.ContainsKey('Path')) { Write-Verbose -Message ( $script:localizedData.CreateComputerAccountInPath -f $ComputerName, $Path ) $newADComputerParameters['Path'] = $Path } else { Write-Verbose -Message ( $script:localizedData.CreateComputerAccount -f $ComputerName ) } <# If the parameter EnabledOnCreation is specified, then the property Enabled is set to that value. #> if ($PSBoundParameters.ContainsKey('EnabledOnCreation')) { if ($EnabledOnCreation) { Write-Verbose -Message ($script:localizedData.EnabledComputerAccount -f $ComputerName) } else { Write-Verbose -Message ($script:localizedData.DisabledComputerAccount -f $ComputerName) } $newADComputerParameters['Enabled'] = $EnabledOnCreation } New-ADComputer @newADComputerParameters } } <# Now retrieve the newly created computer account so the other properties can be set if specified. #> $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters } <# - Ignores the parameter ComputerName since we are not supporting renaming a computer account. - Ignore to compare the parameter ServicePrincipalNames here because it needs a special comparison, so it is handled afterwards. - Ignores the Enabled property because it is not enforced in this resource. #> $compareTargetResourceStateParameters = @{ CurrentValues = $getTargetResourceResult DesiredValues = $PSBoundParameters # This gives an array of properties to compare. Properties = $script:computerObjectPropertyMap.ParameterName # But these properties IgnoreProperties = @( 'ComputerName' 'ServicePrincipalNames' 'Enabled' ) } $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters if ($PSBoundParameters.ContainsKey('ServicePrincipalNames')) { $testServicePrincipalNamesParameters = @{ ExistingServicePrincipalNames = $getTargetResourceResult.ServicePrincipalNames ServicePrincipalNames = $ServicePrincipalNames } $compareTargetResourceStateResult += @{ ParameterName = 'ServicePrincipalNames' Expected = $testServicePrincipalNamesParameters.ServicePrincipalNames Actual = $testServicePrincipalNamesParameters.ExistingServicePrincipalNames InDesiredState = Test-ServicePrincipalNames @testServicePrincipalNamesParameters } } $commonParameters = Get-ADCommonParameters @PSBoundParameters if ($compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'Path' -and -not $_.InDesiredState })) { <# Must move the computer account since we can't simply update the DistinguishedName property It does not work moving the computer account using the SamAccountName as the identity, so using the property DistinguishedName instead. #> $moveADObjectParameters = $commonParameters.Clone() $moveADObjectParameters['Identity'] = $getTargetResourceResult.DistinguishedName Write-Verbose -Message ( $script:localizedData.MovingComputerAccount -f $ComputerName, $getTargetResourceResult.Path, $Path ) Move-ADObject @moveADObjectParameters -TargetPath $Path } $replaceComputerProperties = @{} $removeComputerProperties = @{} # Get all properties, other than Path, that is not in desired state. $propertiesNotInDesiredState = $compareTargetResourceStateResult | Where-Object -FilterScript { $_.ParameterName -ne 'Path' -and -not $_.InDesiredState } foreach ($property in $propertiesNotInDesiredState) { $computerAccountPropertyName = ($script:computerObjectPropertyMap | Where-Object -FilterScript { $_.ParameterName -eq $property.ParameterName }).PropertyName if (-not $computerAccountPropertyName) { $computerAccountPropertyName = $property.ParameterName } if ($property.Expected) { Write-Verbose -Message ( $script:localizedData.UpdatingComputerAccountProperty -f $computerAccountPropertyName, ($property.Expected -join ''',''') ) # Replace the current value. $replaceComputerProperties[$computerAccountPropertyName] = $property.Expected } else { Write-Verbose -Message ( $script:localizedData.RemovingComputerAccountProperty -f $property.ParameterName, ($property.Actual -join ''',''') ) # Remove the current value since the desired value is empty or nothing. $removeComputerProperties[$computerAccountPropertyName] = $property.Actual } } $setADComputerParameters = $commonParameters.Clone() # Set-ADComputer is only called if we have something to change. if ($replaceComputerProperties.Count -gt 0 -or $removeComputerProperties.Count -gt 0) { if ($replaceComputerProperties.Count -gt 0) { $setADComputerParameters['Replace'] = $replaceComputerProperties } if ($removeComputerProperties.Count -gt 0) { $setADComputerParameters['Remove'] = $removeComputerProperties } Set-ADComputer @setADComputerParameters Write-Verbose -Message ( $script:localizedData.UpdatedComputerAccount -f $ComputerName ) } } elseif ($Ensure -eq 'Absent' -and $getTargetResourceResult.Ensure -eq 'Present') { # User exists and needs removing Write-Verbose -Message ( $script:localizedData.RemovingComputerAccount -f $ComputerName ) $removeADComputerParameters = Get-ADCommonParameters @PSBoundParameters $removeADComputerParameters['Confirm'] = $false Remove-ADComputer @removeADComputerParameters | Out-Null } } <# .SYNOPSIS This evaluates the service principal names current state against the desired state. .PARAMETER ExistingServicePrincipalNames An array of existing service principal names that should be compared against the array in parameter ServicePrincipalNames. .PARAMETER ServicePrincipalNames An array of the desired service principal names that should be compared against the array in parameter ExistingServicePrincipalNames. #> function Test-ServicePrincipalNames { param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [System.String[]] $ExistingServicePrincipalNames, [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [AllowEmptyString()] [System.String[]] $ServicePrincipalNames ) $testServicePrincipalNamesReturnValue = $true $testMembersParameters = @{ ExistingMembers = $ExistingServicePrincipalNames Members = $ServicePrincipalNames } if (-not (Test-Members @testMembersParameters)) { Write-Verbose -Message ( $script:localizedData.ServicePrincipalNamesNotInDesiredState ` -f ($ExistingServicePrincipalNames -join ','), ($ServicePrincipalNames -join ',') ) $testServicePrincipalNamesReturnValue = $false } else { Write-Verbose -Message ( $script:localizedData.ServicePrincipalNamesInDesiredState ) } return $testServicePrincipalNamesReturnValue } Export-ModuleMember -Function *-TargetResource |