Skip to content

Commit

Permalink
Changes to xActiveDirectory
Browse files Browse the repository at this point in the history
- Added new helper functions in xActiveDirectory.Common.
  - New-CimCredentialInstance
  - Add-TypeAssembly
  - New-ADDirectoryContext
  • Loading branch information
johlju committed Jul 10, 2019
1 parent 20a097c commit 4d44b4a
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ ConvertFrom-StringData @'
ArrayValueThatDoesNotMatch = {0} - {1} (ADCOMMON0007)
PropertyValueOfTypeDoesNotMatch = {0} value does not match. Current value is '{1}', but expected the value '{2}'. (ADCOMMON0008)
UnableToCompareType = Unable to compare the type {0} as it is not handled by the Test-DscPropertyState cmdlet. (ADCOMMON0009)
RoleNotFoundError = Please ensure that the PowerShell module for role '{0}' is installed. (ADCOMMON0010)
ModuleNotFoundError = Please ensure that the PowerShell module for role '{0}' is installed. (ADCOMMON0010)
MembersAndIncludeExcludeError = The '{0}' and '{1}' and/or '{2}' parameters conflict. The '{0}' parameter should not be used in any combination with the '{1}' and '{2}' parameters. (ADCOMMON0011)
MembersIsNullError = The Members parameter value is null. The '{0}' parameter must be provided if neither '{1}' nor '{2}' is provided. (ADCOMMON0012)
IncludeAndExcludeConflictError = The member '{0}' is included in both '{1}' and '{2}' parameter values. The same member must not be included in both '{1}' and '{2}' parameter values. (ADCOMMON0014)
Expand Down Expand Up @@ -39,4 +39,11 @@ ConvertFrom-StringData @'
ValueOfTypeDoesNotMatch = {0} value for property {1} does not match. Current state is '{2}' and desired state is '{3}'. (ADCOMMON0039)
UnableToCompareProperty = Unable to compare property {0} as the type {1} is not handled by the Test-DscParameterState cmdlet. (ADCOMMON0040)
StartProcess = Started the process with id {0} using the path '{1}', and with a timeout value of {2} seconds. (ADCOMMON0041)
CouldNotLoadAssembly = The assembly '{0}' could not be loaded into the PowerShell session. (ADCOMMON0042)
TypeAlreadyExistInSession = The type '{0}' is already loaded into the PowerShell session. (ADCOMMON0043)
TypeDoesNotExistInSession = Missing the type '{0}' from the PowerShell session. (ADCOMMON0044)
AddingAssemblyToSession = Adding the assembly '{0}' into the PowerShell session. (ADCOMMON0045)
NewDirectoryContext = Get a new Active Directory context of the type '{0}'. (ADCOMMON0046)
NewDirectoryContextTarget = The Active Directory context will target '{0}'. (ADCOMMON0047)
NewDirectoryContextCredential = The Active Directory context will be accessed using the '{0}' credentials. (ADCOMMON0048)
'@
189 changes: 183 additions & 6 deletions Modules/xActiveDirectory.Common/xActiveDirectory.Common.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ function Assert-Module
if (-not (Get-Module -Name $ModuleName -ListAvailable))
{
$errorId = '{0}_ModuleNotFound' -f $ModuleName
$errorMessage = $script:localizedData.RoleNotFoundError -f $moduleName
$errorMessage = $script:localizedData.ModuleNotFoundError -f $moduleName
ThrowInvalidOperationError -ErrorId $errorId -ErrorMessage $errorMessage
}

Expand Down Expand Up @@ -1546,10 +1546,9 @@ function Convert-PropertyMapToObjectProperties
values. Normally set to $PSBoundParameters.
.PARAMETER Properties
An array of property names to filter out from the keys provided in
DesiredValues. If left out, only those keys in the DesiredValues will
be compared. This parameter can be used to remove certain keys from
the comparison.
An array of property names, from the keys provided in DesiredValues, that
will be compared. If this parameter is left out, all the keys in the
DesiredValues will be compared.
#>
function Compare-ResourcePropertyState
{
Expand Down Expand Up @@ -1776,7 +1775,8 @@ function Assert-ADPSDrive

if ($null -eq $activeDirectoryPSDrive)
{
Write-Verbose -Message $script:localizedData.CreatingNewADPSDrive
Write-Verbose -Message $script:localizedData.CreatingNewADPSDrive -Verbose

try
{
New-PSDrive -Name AD -PSProvider 'ActiveDirectory' -Root $Root -Scope Script -ErrorAction 'Stop' |
Expand Down Expand Up @@ -1814,6 +1814,180 @@ function Set-DscADComputer
Set-ADComputer @Parameters | Out-Null
}

<#
.SYNOPSIS
This returns a new MSFT_Credential CIM instance credential object to be
used when returning credential objects from Get-TargetResource.
This returns a credential object without the password.
.PARAMETER Credential
The PSCredential object to return as a MSFT_Credential CIM instance
credential object.
.NOTES
When returning a PSCredential object from Get-TargetResource, the
credential object does not contain the username. The object is empty.
Password UserName PSComputerName
-------- -------- --------------
localhost
When the MSFT_Credential CIM instance credential object is returned by
the Get-TargetResource then the credential object contains the values
provided in the object.
Password UserName PSComputerName
-------- -------- --------------
COMPANY\TestAccount localhost
#>
function New-CimCredentialInstance
{
[CmdletBinding()]
[OutputType([Microsoft.Management.Infrastructure.CimInstance])]
param
(
[Parameter(Mandatory = $true)]
[System.Management.Automation.PSCredential]
$Credential
)

$newCimInstanceParameters = @{
ClassName = 'MSFT_Credential'
ClientOnly = $true
Namespace = 'root/microsoft/windows/desiredstateconfiguration'
Property = @{
UserName = [System.String] $Credential.UserName
Password = [System.String] $null
}
}

return New-CimInstance @newCimInstanceParameters
}

<#
.SYNOPSIS
This loads the assembly type, optionally after a check
if the type is missing in the PowerShell session.
.PARAMETER AssemblyName
The assembly to load into the PowerShell session.
.PARAMETER TypeName
An optional parameter to check if the type exist, if it exist then the
assembly is not loaded again.
#>
function Add-TypeAssembly
{
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)]
[System.String]
$AssemblyName,

[Parameter()]
[System.String]
$TypeName
)

if ($PSBoundParameters.ContainsKey('TypeName'))
{
if ($TypeName -as [Type])
{
Write-Verbose -Message ($script:localizedData.TypeAlreadyExistInSession -f $TypeName) -Verbose

# The type already exist so no need to load the type again.
return
}
else
{
Write-Verbose -Message ($script:localizedData.TypeDoesNotExistInSession -f $TypeName) -Verbose
}
}

try
{
Write-Verbose -Message ($script:localizedData.AddingAssemblyToSession -f $AssemblyName) -Verbose

Add-Type -AssemblyName $AssemblyName
}
catch
{
$missingRoleMessage = $script:localizedData.CouldNotLoadAssembly -f $AssemblyName
New-ObjectNotFoundException -Message $missingRoleMessage -ErrorRecord $_
}
}

<#
.SYNOPSIS
This returns a new object of the type System.DirectoryServices.ActiveDirectory.DirectoryContext.
.PARAMETER DirectoryContextType
The context type of the object to return. Valid values are 'Domain', 'Forest',
'ApplicationPartition', 'ConfigurationSet' or 'DirectoryServer'.
.PARAMETER Name
An optional parameter for the target of the directory context.
For the correct format for this parameter depending on context type, see
the article https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.activedirectory.directorycontext?view=netframework-4.8
#>
function Get-ADDirectoryContext
{
[CmdletBinding()]
[OutputType([System.DirectoryServices.ActiveDirectory.DirectoryContext])]
param
(
[Parameter(Mandatory = $true)]
[ValidateSet('Domain', 'Forest', 'ApplicationPartition', 'ConfigurationSet', 'DirectoryServer')]
[System.String]
$DirectoryContextType,

[Parameter()]
[System.String]
$Name,

[Parameter()]
[System.Management.Automation.PSCredential]
$Credential
)

$typeName = 'System.DirectoryServices.ActiveDirectory.DirectoryContext'

Add-TypeAssembly -AssemblyName 'System.DirectoryServices' -TypeName $typeName

Write-Verbose -Message ($script:localizedData.NewDirectoryContext -f $DirectoryContextType) -Verbose

$newObjectArgumentList = @(
$DirectoryContextType
)

if ($PSBoundParameters.ContainsKey('Name'))
{
Write-Verbose -Message ($script:localizedData.NewDirectoryContextTarget -f $Name) -Verbose

$newObjectArgumentList += @(
$Name
)
}

if ($PSBoundParameters.ContainsKey('Credential'))
{
Write-Verbose -Message ($script:localizedData.NewDirectoryContextCredential -f $Credential.UserName) -Verbose

$newObjectArgumentList += @(
$Credential.UserName
$Credential.GetNetworkCredential().Password
)
}

$newObjectParameters = @{
TypeName = $typeName
ArgumentList = $newObjectArgumentList
}

return New-Object @newObjectParameters
}

$script:localizedData = Get-LocalizedData -ResourceName 'xActiveDirectory.Common' -ScriptRoot $PSScriptRoot

Export-ModuleMember -Function @(
Expand Down Expand Up @@ -1850,4 +2024,7 @@ Export-ModuleMember -Function @(
'Test-DscPropertyState'
'Assert-ADPSDrive'
'Set-DscADComputer'
'New-CimCredentialInstance'
'Add-TypeAssembly'
'Get-ADDirectoryContext'
)
131 changes: 131 additions & 0 deletions Tests/Unit/xActiveDirectory.Common.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2115,4 +2115,135 @@ InModuleScope 'xActiveDirectory.Common' {
}
}
}

Describe 'xActiveDirectory.CommonNew-CimCredentialInstance' {
Context 'When creating a new MSFT_Credential CIM instance credential object' {
BeforeAll {
$mockAdministratorUser = '[email protected]'
$mockAdministratorPassword = 'P@ssw0rd-12P@ssw0rd-12'
$mockAdministratorCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @(
$mockAdministratorUser,
($mockAdministratorPassword | ConvertTo-SecureString -AsPlainText -Force)
)
}

It 'Should return the correct values' {
$newCimCredentialInstanceResult = New-CimCredentialInstance -Credential $mockAdministratorCredential
$newCimCredentialInstanceResult | Should -BeOfType 'Microsoft.Management.Infrastructure.CimInstance'
$newCimCredentialInstanceResult.CimClass.CimClassName | Should -Be 'MSFT_Credential'
$newCimCredentialInstanceResult.UserName | Should -Be $mockAdministratorUser
$newCimCredentialInstanceResult.Password | Should -BeNullOrEmpty
}
}
}

Describe 'xActiveDirectory.Common\Add-TypeAssembly' {
Context 'When assembly fails to load' {
BeforeAll {
Mock -CommandName Add-Type -MockWith {
throw
}

$mockAssembly = 'MyAssembly'
}

It 'Should throw the correct error' {
{ Add-TypeAssembly -AssemblyName $mockAssembly } | Should -Throw ($script:localizedData.CouldNotLoadAssembly -f $mockAssembly)
}
}

Context 'When loading an assembly into the session' {
BeforeAll {
Mock -CommandName Add-Type

$mockAssembly = 'MyAssembly'
}

It 'Should not throw and call the correct mocks' {
{ Add-TypeAssembly -AssemblyName $mockAssembly } | Should -Not -Throw

Assert-MockCalled -CommandName Add-Type -ParameterFilter {
$AssemblyName -eq $mockAssembly
} -Exactly -Times 1 -Scope It
}

Context 'When the type is already loaded into the session' {
It 'Should not throw and not call any mocks' {
{ Add-TypeAssembly -AssemblyName $mockAssembly -TypeName 'System.String' } | Should -Not -Throw

Assert-MockCalled -CommandName Add-Type -Exactly -Times 0 -Scope It
}
}

Context 'When the type is missing from the session' {
It 'Should not throw and call the correct mocks' {
{ Add-TypeAssembly -AssemblyName $mockAssembly -TypeName 'My.Type' } | Should -Not -Throw

Assert-MockCalled -CommandName Add-Type -ParameterFilter {
$AssemblyName -eq $mockAssembly
} -Exactly -Times 1 -Scope It
}
}
}
}

Describe 'xActiveDirectory.Common\New-ADDirectoryContext' {
Context 'When creating a new Active Directory context' {
BeforeAll {
# This credential object must be created before we mock New-Object.
$mockAdministratorUser = '[email protected]'
$mockAdministratorPassword = 'P@ssw0rd-12P@ssw0rd-12'
$mockAdministratorCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @(
$mockAdministratorUser,
($mockAdministratorPassword | ConvertTo-SecureString -AsPlainText -Force)
)

Mock -CommandName Add-TypeAssembly -Verifiable
Mock -CommandName New-Object
}

Context 'When the calling with only parameter DirectoryContextType' {
It 'Should not throw and call the correct mocks' {
{ Get-ADDirectoryContext -DirectoryContextType 'Domain' } | Should -Not -Throw

Assert-MockCalled -CommandName New-Object -ParameterFilter {
$ArgumentList.Count -eq 1 `
-and $ArgumentList[0] -eq 'Domain'
} -Exactly -Times 1 -Scope It
}
}

Context 'When the calling with parameters DirectoryContextType and Name' {
It 'Should not throw and call the correct mocks' {
{
Get-ADDirectoryContext -DirectoryContextType 'Domain' -Name 'my.domain'
} | Should -Not -Throw

Assert-MockCalled -CommandName New-Object -ParameterFilter {
$ArgumentList.Count -eq 2 `
-and $ArgumentList[0] -eq 'Domain' `
-and $ArgumentList[1] -eq 'my.domain'
} -Exactly -Times 1 -Scope It
}
}

Context 'When the calling with parameters DirectoryContextType, Name and Credential' {
It 'Should not throw and call the correct mocks' {
{
Get-ADDirectoryContext -DirectoryContextType 'Domain' -Name 'my.domain' -Credential $mockAdministratorCredential
} | Should -Not -Throw

Assert-MockCalled -CommandName New-Object -ParameterFilter {
$ArgumentList.Count -eq 4 `
-and $ArgumentList[0] -eq 'Domain' `
-and $ArgumentList[1] -eq 'my.domain' `
-and $ArgumentList[2] -eq $mockAdministratorUser `
-and $ArgumentList[3] -eq $mockAdministratorPassword
} -Exactly -Times 1 -Scope It
}
}

Assert-VerifiableMock
}
}
}

0 comments on commit 4d44b4a

Please sign in to comment.