diff --git a/.MetaTestOptIn.json b/.MetaTestOptIn.json index a74f3590b..b1c88b6f8 100644 --- a/.MetaTestOptIn.json +++ b/.MetaTestOptIn.json @@ -1,3 +1,6 @@ [ - "Common Tests - Validate Markdown Files" + "Common Tests - Validate Markdown Files", + "Common Tests - Custom Script Analyzer Rules", + "Common Tests - Required Script Analyzer Rules", + "Common Tests - Flagged Script Analyzer Rules" ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 78ad87642..7d6dc0df8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ - Compare-ResourcePropertyState - Test-DscPropertyState - Move the examples in the README.md to Examples folder + - Fix Script Analyzer rule failures + - Opt-in to "Common Tests - Custom Script Analyzer Rules" + - Opt-in to "Common Tests - Required Script Analyzer Rules" + - Opt-in to "Common Tests - Flagged Script Analyzer Rules" - Changes to xADComputer - Refactored the resource and the unit tests. - BREAKING CHANGE: The `Enabled` property is **DEPRECATED** and is no diff --git a/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.psm1 b/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.psm1 index 43251bf68..36dceddb5 100644 --- a/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.psm1 +++ b/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.psm1 @@ -38,7 +38,7 @@ function Get-TargetResource [OutputType([System.Collections.Hashtable])] param ( - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $DomainName, [Parameter()] @@ -77,7 +77,7 @@ function Test-TargetResource [OutputType([System.Boolean])] param ( - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $DomainName, [Parameter()] @@ -163,7 +163,7 @@ function Set-TargetResource [CmdletBinding()] param ( - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $DomainName, [Parameter()] diff --git a/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 b/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 index df3d719df..afa99718a 100644 --- a/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 +++ b/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 @@ -1,23 +1,10 @@ -# Localized messages -data LocalizedData -{ - # culture="en-US" - ConvertFrom-StringData @' -MissingRoleMessage = Please ensure that the {0} role is installed - -CheckingTrustMessage = Checking if Trust between {0} and {1} exists ... -TestTrustMessage = Trust is {0} between source and target domains and it should be {1} -RemovingTrustMessage = Removing trust between {0} and {1} domains ... -DeleteTrustMessage = Trust between specified domains is now absent -AddingTrustMessage = Adding domain trust between {0} and {1} ... -SetTrustMessage = Trust between specified domains is now present - -CheckPropertyMessage = Checking for {0} between domains ... -DesiredPropertyMessage = {0} between domains is set correctly -NotDesiredPropertyMessage = {0} between domains is not correct. Expected {1}, actual {2} -SetPropertyMessage = {0} between domains is set -'@ -} +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADDomainTrust' function Get-TargetResource { @@ -25,23 +12,24 @@ function Get-TargetResource [OutputType([System.Collections.Hashtable])] param ( - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$SourceDomainName, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$TargetDomainName, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [PSCredential]$TargetDomainAdministratorCredential, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [ValidateSet("External","Forest")] [String]$TrustType, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [ValidateSet("Bidirectional","Inbound","Outbound")] [String]$TrustDirection, + [Parameter()] [ValidateSet("Present","Absent")] [String]$Ensure = 'Present' ) @@ -56,7 +44,7 @@ function Get-TargetResource # If not found, means ADDS role is not installed catch { - $missingRoleMessage = $($LocalizedData.MissingRoleMessage) -f 'AD-Domain-Services' + $missingRoleMessage = $($script:localizedData.MissingRoleMessage) -f 'AD-Domain-Services' New-TerminatingError -errorId ActiveDirectoryRoleMissing -errorMessage $missingRoleMessage -errorCategory NotInstalled } @@ -76,13 +64,16 @@ function Get-TargetResource $srcDirectoryContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext($DomainOrForest,$SourceDomainName) $srcDomain = ([type]"System.DirectoryServices.ActiveDirectory.$DomainOrForest")::"Get$DomainOrForest"($srcDirectoryContext) - # Find trust betwen source & destination. + # Find trust between source & destination. + Write-Verbose -Message ($script:localizedData.CheckingTrustMessage -f $SourceDomainName, $TargetDomainName) $trust = $srcDomain.GetTrustRelationship($trgDomain) + Write-Verbose -Message ($script:localizedData.TrustPresentMessage -f $SourceDomainName, $TargetDomainName) $Ensure = 'Present' } catch { + Write-Verbose -Message ($script:localizedData.TrustAbsentMessage -f $SourceDomainName, $TargetDomainName) $Ensure = 'Absent' } @@ -107,57 +98,63 @@ function Get-TargetResource function Set-TargetResource { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "", + Justification = 'Verbose messaging in helper function')] [CmdletBinding()] param ( - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$SourceDomainName, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$TargetDomainName, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [PSCredential]$TargetDomainAdministratorCredential, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [ValidateSet("External","Forest")] [String]$TrustType, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [ValidateSet("Bidirectional","Inbound","Outbound")] [String]$TrustDirection, + [Parameter()] [ValidateSet("Present","Absent")] [String]$Ensure = 'Present' ) if($PSBoundParameters.ContainsKey('Debug')){$null = $PSBoundParameters.Remove('Debug')} - Validate-ResourceProperties @PSBoundParameters -Apply + Confirm-ResourceProperties @PSBoundParameters -Apply } function Test-TargetResource { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "", + Justification = 'Verbose messaging in helper function')] [CmdletBinding()] [OutputType([System.Boolean])] param ( - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$SourceDomainName, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$TargetDomainName, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [PSCredential]$TargetDomainAdministratorCredential, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [ValidateSet("External","Forest")] [String]$TrustType, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [ValidateSet("Bidirectional","Inbound","Outbound")] [String]$TrustDirection, + [Parameter()] [ValidateSet("Present","Absent")] [String]$Ensure = 'Present' ) @@ -172,48 +169,51 @@ function Test-TargetResource # If not found, means ADDS role is not installed catch { - $missingRoleMessage = $($LocalizedData.MissingRoleMessage) -f 'AD-Domain-Services' + $missingRoleMessage = $($script:localizedData.MissingRoleMessage) -f 'AD-Domain-Services' New-TerminatingError -errorId ActiveDirectoryRoleMissing -errorMessage $missingRoleMessage -errorCategory NotInstalled } #endregion if($PSBoundParameters.ContainsKey('Debug')){$null = $PSBoundParameters.Remove('Debug')} - Validate-ResourceProperties @PSBoundParameters + Confirm-ResourceProperties @PSBoundParameters } #region Helper Functions -function Validate-ResourceProperties +function Confirm-ResourceProperties { [Cmdletbinding()] + [OutputType([System.Boolean])] param ( - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$SourceDomainName, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$TargetDomainName, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [PSCredential]$TargetDomainAdministratorCredential, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [ValidateSet("External","Forest")] [String]$TrustType, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [ValidateSet("Bidirectional","Inbound","Outbound")] [String]$TrustDirection, + [Parameter()] [ValidateSet("Present","Absent")] [String]$Ensure = 'Present', + [Parameter()] [Switch]$Apply ) try { - $checkingTrustMessage = $($LocalizedData.CheckingTrustMessage) -f $SourceDomainName,$TargetDomainName + $checkingTrustMessage = $($script:localizedData.CheckingTrustMessage) -f $SourceDomainName,$TargetDomainName Write-Verbose -Message $checkingTrustMessage switch ($TrustType) @@ -233,28 +233,28 @@ function Validate-ResourceProperties { # Find trust betwen source & destination. $trust = $srcDomain.GetTrustRelationship($TargetDomainName) - - $TestTrustMessage = $($LocalizedData.TestTrustMessage) -f 'present',$Ensure + + $TestTrustMessage = $($script:localizedData.TestTrustMessage) -f 'present',$Ensure Write-Verbose -Message $TestTrustMessage if($Ensure -eq 'Present') { #region Test for trust direction - $CheckPropertyMessage = $($LocalizedData.CheckPropertyMessage) -f 'trust direction' + $CheckPropertyMessage = $($script:localizedData.CheckPropertyMessage) -f 'trust direction' Write-Verbose -Message $CheckPropertyMessage - + # Set the trust direction if not correct if($trust.TrustDirection -ne $TrustDirection) { - $notDesiredPropertyMessage = $($LocalizedData.NotDesiredPropertyMessage) -f 'Trust direction',$TrustDirection,$trust.TrustDirection + $notDesiredPropertyMessage = $($script:localizedData.NotDesiredPropertyMessage) -f 'Trust direction',$TrustDirection,$trust.TrustDirection Write-Verbose -Message $notDesiredPropertyMessage if($Apply) { $srcDomain.UpdateTrustRelationship($trgDomain,$TrustDirection) - $setPropertyMessage = $($LocalizedData.SetPropertyMessage) -f 'Trust direction' + $setPropertyMessage = $($script:localizedData.SetPropertyMessage) -f 'Trust direction' Write-Verbose -Message $setPropertyMessage } else @@ -262,24 +262,24 @@ function Validate-ResourceProperties return $false } } # end trust direction is not correct - + # Trust direction is correct else { - $desiredPropertyMessage = $($LocalizedData.DesiredPropertyMessage) -f 'Trust direction' + $desiredPropertyMessage = $($script:localizedData.DesiredPropertyMessage) -f 'Trust direction' Write-Verbose -Message $desiredPropertyMessage } #endregion trust direction - + #region Test for trust type - $CheckPropertyMessage = $($LocalizedData.CheckPropertyMessage) -f 'trust type' + $CheckPropertyMessage = $($script:localizedData.CheckPropertyMessage) -f 'trust type' Write-Verbose -Message $CheckPropertyMessage - + # Set the trust type if not correct if($trust.TrustType-ne $TrustType) { - $notDesiredPropertyMessage = $($LocalizedData.NotDesiredPropertyMessage) -f 'Trust type',$TrustType,$trust.TrustType + $notDesiredPropertyMessage = $($script:localizedData.NotDesiredPropertyMessage) -f 'Trust type',$TrustType,$trust.TrustType Write-Verbose -Message $notDesiredPropertyMessage if($Apply) @@ -289,7 +289,7 @@ function Validate-ResourceProperties $srcDomain.DeleteTrustRelationship($trgDomain) $srcDomain.CreateTrustRelationship($trgDomain,$TrustDirection) - $setPropertyMessage = $($LocalizedData.SetPropertyMessage) -f 'Trust type' + $setPropertyMessage = $($script:localizedData.SetPropertyMessage) -f 'Trust type' Write-Verbose -Message $setPropertyMessage } else @@ -297,11 +297,11 @@ function Validate-ResourceProperties return $false } } # end trust type is not correct - + # Trust type is correct else { - $desiredPropertyMessage = $($LocalizedData.DesiredPropertyMessage) -f 'Trust type' + $desiredPropertyMessage = $($script:localizedData.DesiredPropertyMessage) -f 'Trust type' Write-Verbose -Message $desiredPropertyMessage } @@ -311,20 +311,20 @@ function Validate-ResourceProperties if(-not $Apply) { return $true - } + } } # end Ensure -eq present - + # If the trust should be absent, remove the trust else - { + { if($Apply) { - $removingTrustMessage = $($LocalizedData.RemovingTrustMessage) -f $SourceDomainName,$TargetDomainName + $removingTrustMessage = $($script:localizedData.RemovingTrustMessage) -f $SourceDomainName,$TargetDomainName Write-Verbose -Message $removingTrustMessage $srcDomain.DeleteTrustRelationship($trgDomain) - $deleteTrustMessage = $LocalizedData.DeleteTrustMessage + $deleteTrustMessage = $script:localizedData.DeleteTrustMessage Write-Verbose -Message $deleteTrustMessage } else @@ -337,19 +337,19 @@ function Validate-ResourceProperties # Trust does not exist between source and destination catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] { - $TestTrustMessage = $($LocalizedData.TestTrustMessage) -f 'absent',$Ensure + $TestTrustMessage = $($script:localizedData.TestTrustMessage) -f 'absent',$Ensure Write-Verbose -Message $TestTrustMessage if($Ensure -eq 'Present') { if($Apply) { - $addingTrustMessage = $($LocalizedData.AddingTrustMessage) -f $SourceDomainName,$TargetDomainName + $addingTrustMessage = $($script:localizedData.AddingTrustMessage) -f $SourceDomainName,$TargetDomainName Write-Verbose -Message $addingTrustMessage - + $srcDomain.CreateTrustRelationship($trgDomain,$TrustDirection) - $setTrustMessage = $LocalizedData.SetTrustMessage + $setTrustMessage = $script:localizedData.SetTrustMessage Write-Verbose -Message $setTrustMessage } else @@ -378,17 +378,17 @@ function New-TerminatingError [CmdletBinding()] param ( - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$errorId, - - [Parameter(Mandatory)] + + [Parameter(Mandatory = $true)] [String]$errorMessage, - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.Management.Automation.ErrorCategory]$errorCategory ) - - $exception = New-Object System.InvalidOperationException $errorMessage + + $exception = New-Object System.InvalidOperationException $errorMessage $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null throw $errorRecord } diff --git a/DSCResources/MSFT_xADDomainTrust/en-US/MSFT_xADDomainTrust.strings.psd1 b/DSCResources/MSFT_xADDomainTrust/en-US/MSFT_xADDomainTrust.strings.psd1 new file mode 100644 index 000000000..0abd847b0 --- /dev/null +++ b/DSCResources/MSFT_xADDomainTrust/en-US/MSFT_xADDomainTrust.strings.psd1 @@ -0,0 +1,15 @@ +ConvertFrom-StringData @' +MissingRoleMessage = Please ensure that the {0} role is installed +CheckingTrustMessage = Checking if Trust between {0} and {1} exists ... +TestTrustMessage = Trust is {0} between source and target domains and it should be {1} +RemovingTrustMessage = Removing trust between {0} and {1} domains ... +DeleteTrustMessage = Trust between specified domains is now absent +AddingTrustMessage = Adding domain trust between {0} and {1} ... +SetTrustMessage = Trust between specified domains is now present +CheckPropertyMessage = Checking for {0} between domains ... +DesiredPropertyMessage = {0} between domains is set correctly +NotDesiredPropertyMessage = {0} between domains is not correct. Expected {1}, actual {2} +SetPropertyMessage = {0} between domains is set +TrustPresentMessage = Trust between domains {0} and {1} is present +TrustAbsentMessage = Trust between domains {0} and {1} is absent +'@ diff --git a/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.psm1 b/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.psm1 index ae85a63f5..769df5659 100644 --- a/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.psm1 +++ b/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.psm1 @@ -28,10 +28,10 @@ function Get-TargetResource [OutputType([System.Collections.Hashtable])] param ( - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Name, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Path ) @@ -56,27 +56,32 @@ function Test-TargetResource [OutputType([System.Boolean])] param ( - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Name, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Path, + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', + [Parameter()] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential, + [Parameter()] [ValidateNotNull()] [System.Boolean] $ProtectedFromAccidentalDeletion = $true, + [Parameter()] [ValidateNotNull()] [System.String] $Description = '', + [Parameter()] [ValidateNotNull()] [System.Boolean] $RestoreFromRecycleBin @@ -89,12 +94,14 @@ function Test-TargetResource if ($Ensure -eq 'Present') { ## Organizational unit exists - if ([System.String]::IsNullOrEmpty($Description)) { + if ([System.String]::IsNullOrEmpty($Description)) + { $isCompliant = (($targetResource.Name -eq $Name) -and ($targetResource.Path -eq $Path) -and ($targetResource.ProtectedFromAccidentalDeletion -eq $ProtectedFromAccidentalDeletion)) } - else { + else + { $isCompliant = (($targetResource.Name -eq $Name) -and ($targetResource.Path -eq $Path) -and ($targetResource.ProtectedFromAccidentalDeletion -eq $ProtectedFromAccidentalDeletion) -and @@ -140,27 +147,32 @@ function Set-TargetResource [CmdletBinding()] param ( - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Name, - [parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Path, + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', + [Parameter()] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential, + [Parameter()] [ValidateNotNull()] [System.Boolean] $ProtectedFromAccidentalDeletion = $true, + [Parameter()] [ValidateNotNull()] [System.String] $Description = '', + [Parameter()] [ValidateNotNull()] [System.Boolean] $RestoreFromRecycleBin @@ -242,7 +254,8 @@ function Set-TargetResource Description = $Description ProtectedFromAccidentalDeletion = $ProtectedFromAccidentalDeletion } - if ($Credential) { + if ($Credential) + { $newADOrganizationalUnitParams['Credential'] = $Credential } New-ADOrganizationalUnit @newADOrganizationalUnitParams diff --git a/DSCResources/MSFT_xADRecycleBin/MSFT_xADRecycleBin.psm1 b/DSCResources/MSFT_xADRecycleBin/MSFT_xADRecycleBin.psm1 index d64268fbc..ced04758e 100644 --- a/DSCResources/MSFT_xADRecycleBin/MSFT_xADRecycleBin.psm1 +++ b/DSCResources/MSFT_xADRecycleBin/MSFT_xADRecycleBin.psm1 @@ -1,14 +1,22 @@ +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADRecycleBin' + function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $ForestFQDN, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $EnterpriseAdministratorCredential ) @@ -23,23 +31,29 @@ function Get-TargetResource $msDSEnabledFeature = Get-ADObject -Identity "CN=Partitions,$($RootDSE.configurationNamingContext)" -Property msDS-EnabledFeature -Server $ForestFQDN -Credential $EnterpriseAdministratorCredential | Select-Object -ExpandProperty msDS-EnabledFeature - If ($msDSEnabledFeature -contains $RecycleBinPath) { + If ($msDSEnabledFeature -contains $RecycleBinPath) + { + Write-Verbose -Message $script:localizedData.RecycleBinEnabled $RecycleBinEnabled = $True } Else { + Write-Verbose -Message $script:localizedData.RecycleBinNotEnabled $RecycleBinEnabled = $False } } - Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException],[Microsoft.ActiveDirectory.Management.ADServerDownException] { - Write-Error -Message "Cannot contact forest $ForestFQDN. Check the spelling of the Forest FQDN and make sure that a domain contoller is available on the network." + Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException],[Microsoft.ActiveDirectory.Management.ADServerDownException] + { + Write-Error -Message ($script:localizedData.ForestNotFound -f $ForestFQDN) Throw $_ } - Catch [System.Security.Authentication.AuthenticationException] { - Write-Error -Message "Credential error. Check the username and password used." + Catch [System.Security.Authentication.AuthenticationException] + { + Write-Error -Message $script:localizedData.CredentialError Throw $_ } - Catch { - Write-Error -Message "Unhandled exception getting Recycle Bin status for forest $ForestFQDN." + Catch + { + Write-Error -Message ($script:localizedData.GetUnhandledException -f $ForestFQDN) Throw $_ } @@ -62,11 +76,11 @@ function Set-TargetResource [CmdletBinding(SupportsShouldProcess=$true)] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $ForestFQDN, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $EnterpriseAdministratorCredential ) @@ -80,12 +94,14 @@ function Set-TargetResource $Forest = Get-ADForest -Identity $ForestFQDN -Server $ForestFQDN -Credential $EnterpriseAdministratorCredential # Check minimum forest level and throw if not - If (($Forest.ForestMode -as [int]) -lt 4) { - Write-Verbose -Message "Forest functionality level $($Forest.ForestMode) does not meet minimum requirement of Windows2008R2Forest or greater." - Throw "Forest functionality level $($Forest.ForestMode) does not meet minimum requirement of Windows2008R2Forest or greater." + If (($Forest.ForestMode -as [int]) -lt 4) + { + Write-Verbose -Message ($script:localizedData.ForestFunctionalLevelError -f $Forest.ForestMode) + Throw ($script:localizedData.ForestFunctionalLevelError -f $Forest.ForestMode) } - If ($PSCmdlet.ShouldProcess($Forest.RootDomain, "Enable Active Directory Recycle Bin")) { + If ($PSCmdlet.ShouldProcess($Forest.RootDomain, "Enable Active Directory Recycle Bin")) + { Enable-ADOptionalFeature 'Recycle Bin Feature' -Scope ForestOrConfigurationSet ` -Target $Forest.RootDomain -Server $Forest.DomainNamingMaster ` -Credential $EnterpriseAdministratorCredential ` @@ -93,20 +109,24 @@ function Set-TargetResource } } - Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException],[Microsoft.ActiveDirectory.Management.ADServerDownException] { - Write-Error -Message "Cannot contact forest $ForestFQDN. Check the spelling of the Forest FQDN and make sure that a domain contoller is available on the network." + Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException],[Microsoft.ActiveDirectory.Management.ADServerDownException] + { + Write-Error -Message ($script:localizedData.ForestNotFound -f $ForestFQDN) Throw $_ } - Catch [System.Security.Authentication.AuthenticationException] { - Write-Error -Message "Credential error. Check the username and password used." + Catch [System.Security.Authentication.AuthenticationException] + { + Write-Error -Message $script:localizedData.CredentialError Throw $_ } - Catch { - Write-Error -Message "Unhandled exception setting Recycle Bin status for forest $ForestFQDN." + Catch + { + Write-Error -Message ($script:localizedData.SetUnhandledException -f $ForestFQDN) Throw $_ } - Finally { + Finally + { $ErrorActionPreference = 'Continue' } @@ -119,16 +139,17 @@ function Test-TargetResource [OutputType([System.Boolean])] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $ForestFQDN, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $EnterpriseAdministratorCredential ) - Try { + Try + { # AD cmdlets generate non-terminating errors. $ErrorActionPreference = 'Stop' @@ -137,29 +158,34 @@ function Test-TargetResource $msDSEnabledFeature = Get-ADObject -Identity "CN=Partitions,$($RootDSE.configurationNamingContext)" -Property msDS-EnabledFeature -Server $ForestFQDN -Credential $EnterpriseAdministratorCredential | Select-Object -ExpandProperty msDS-EnabledFeature - If ($msDSEnabledFeature -contains $RecycleBinPath) { - Write-Verbose "Active Directory Recycle Bin is enabled." + If ($msDSEnabledFeature -contains $RecycleBinPath) + { + Write-Verbose $script:localizedData.RecycleBinEnabled Return $True } Else { - Write-Verbose "Active Directory Recycle Bin is not enabled." + Write-Verbose $script:localizedData.RecycleBinNotEnabled Return $False } } - Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException],[Microsoft.ActiveDirectory.Management.ADServerDownException] { - Write-Error -Message "Cannot contact forest $ForestFQDN. Check the spelling of the Forest FQDN and make sure that a domain contoller is available on the network." + Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException],[Microsoft.ActiveDirectory.Management.ADServerDownException] + { + Write-Error -Message ($script:localizedData.ForestNotFound -f $ForestFQDN) Throw $_ } - Catch [System.Security.Authentication.AuthenticationException] { - Write-Error -Message "Credential error. Check the username and password used." + Catch [System.Security.Authentication.AuthenticationException] + { + Write-Error -Message $script:localizedData.CredentialError Throw $_ } - Catch { - Write-Error -Message "Unhandled exception testing Recycle Bin status for forest $ForestFQDN." + Catch + { + Write-Error -Message ($script:localizedData.TestUnhandledException -f $ForestFQDN) Throw $_ } - Finally { + Finally + { $ErrorActionPreference = 'Continue' } @@ -184,6 +210,3 @@ Get-TargetResource -ForestFQDN contoso.cm -EnterpriseAdministratorCredential $cr Test-TargetResource -ForestFQDN contoso.cm -EnterpriseAdministratorCredential $cred Set-TargetResource -ForestFQDN contoso.cm -EnterpriseAdministratorCredential $cred -WhatIf #> - - - diff --git a/DSCResources/MSFT_xADRecycleBin/en-US/MSFT_xADRecycleBin.strings.psd1 b/DSCResources/MSFT_xADRecycleBin/en-US/MSFT_xADRecycleBin.strings.psd1 new file mode 100644 index 000000000..21ffe37b0 --- /dev/null +++ b/DSCResources/MSFT_xADRecycleBin/en-US/MSFT_xADRecycleBin.strings.psd1 @@ -0,0 +1,11 @@ +# culture='en-US' +ConvertFrom-StringData @' + ForestNotFound = Cannot contact forest '{0}'. Check the spelling of the Forest FQDN and make sure that a domain controller is available on the network. + CredentialError = Credential error. Check the username and password used. + GetUnhandledException = Unhandled exception getting Recycle Bin status for forest '{0}'. + SetUnhandledException = Unhandled exception setting Recycle Bin status for forest '{0}'. + TestUnhandledException = Unhandled exception testing Recycle Bin status for forest '{0}'. + ForestFunctionalLevelError = Forest functional level '{0}' does not meet minimum requirement of Windows2008R2Forest or greater. + RecycleBinEnabled = Active Directory Recycle Bin is enabled. + RecycleBinNotEnabled = Active Directory Recycle Bin is not enabled. +'@ diff --git a/DSCResources/MSFT_xADReplicationSite/MSFT_xADReplicationSite.psm1 b/DSCResources/MSFT_xADReplicationSite/MSFT_xADReplicationSite.psm1 index 6e6a45fd5..0db147091 100644 --- a/DSCResources/MSFT_xADReplicationSite/MSFT_xADReplicationSite.psm1 +++ b/DSCResources/MSFT_xADReplicationSite/MSFT_xADReplicationSite.psm1 @@ -1,3 +1,11 @@ +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADReplicationSite' + <# .SYNOPSIS Returns the current state of the AD replication site. @@ -18,10 +26,12 @@ function Get-TargetResource # Get the replication site filtered by it's name. If the site is not # present, the command will return $null. + Write-Verbose -Message ($script:localizedData.GetReplicationSite -f $Name) $replicationSite = Get-ADReplicationSite -Filter { Name -eq $Name } if ($null -eq $replicationSite) { + Write-Verbose -Message ($script:localizedData.ReplicationSiteAbsent -f $Name) $returnValue = @{ Ensure = 'Absent' Name = $Name @@ -30,6 +40,7 @@ function Get-TargetResource } else { + Write-Verbose -Message ($script:localizedData.ReplicationSitePresent -f $Name) $returnValue = @{ Ensure = 'Present' Name = $Name @@ -85,13 +96,13 @@ function Set-TargetResource #> if ($RenameDefaultFirstSiteName -and ($null -ne $defaultFirstSiteName)) { - Write-Verbose "Add the replication site 'Default-First-Site-Name' to '$Name'" + Write-Verbose -Message ($script:localizedData.AddReplicationSiteDefaultFirstSiteName -f $Name) Rename-ADObject -Identity $defaultFirstSiteName.DistinguishedName -NewName $Name -ErrorAction Stop } else { - Write-Verbose "Add the replication site '$Name'" + Write-Verbose -Message ($script:localizedData.AddReplicationSite -f $Name) New-ADReplicationSite -Name $Name -ErrorAction Stop } @@ -99,7 +110,7 @@ function Set-TargetResource if ($Ensure -eq 'Absent') { - Write-Verbose "Remove the replication site '$Name'" + Write-Verbose -Message ($script:localizedData.RemoveReplicationSite -f $Name) Remove-ADReplicationSite -Identity $Name -Confirm:$false -ErrorAction Stop } @@ -142,5 +153,13 @@ function Test-TargetResource $currentConfiguration = Get-TargetResource -Name $Name + if ($currentConfiguration.Ensure -eq $Ensure) + { + Write-Verbose -Message ($script:localizedData.ReplicationSiteInDesiredState -f $Name) + } + else + { + Write-Verbose -Message ($script:localizedData.ReplicationSiteNotInDesiredState -f $Name) + } return $currentConfiguration.Ensure -eq $Ensure } diff --git a/DSCResources/MSFT_xADReplicationSite/en-US/MSFT_xADReplicationSite.strings.psd1 b/DSCResources/MSFT_xADReplicationSite/en-US/MSFT_xADReplicationSite.strings.psd1 new file mode 100644 index 000000000..769969554 --- /dev/null +++ b/DSCResources/MSFT_xADReplicationSite/en-US/MSFT_xADReplicationSite.strings.psd1 @@ -0,0 +1,11 @@ +# culture='en-US' +ConvertFrom-StringData @' + AddReplicationSiteDefaultFirstSiteName = Add the replication site 'Default-First-Site-Name' to '{0}'. + AddReplicationSite = Add the replication site '{0}'. + RemoveReplicationSite = Remove the replication site '{0}'. + GetReplicationSite = Getting replication site '{0}'. + ReplicationSiteAbsent = Replication site '{0}' is not present. + ReplicationSitePresent = Replication site '{0}' is present. + ReplicationSiteInDesiredState = The replication site '{0}' is in the desired state. + ReplicationSiteNotInDesiredState = The replication site '{0}' is not in the desired state. +'@ diff --git a/DSCResources/MSFT_xADReplicationSubnet/MSFT_xADReplicationSubnet.psm1 b/DSCResources/MSFT_xADReplicationSubnet/MSFT_xADReplicationSubnet.psm1 index 4aba6cff2..0482b1c73 100644 --- a/DSCResources/MSFT_xADReplicationSubnet/MSFT_xADReplicationSubnet.psm1 +++ b/DSCResources/MSFT_xADReplicationSubnet/MSFT_xADReplicationSubnet.psm1 @@ -1,3 +1,11 @@ +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADReplicationSubnet' + <# .SYNOPSIS Returns the current state of the replication subnet. @@ -27,11 +35,13 @@ function Get-TargetResource # Get the replication subnet filtered by it's name. If the subnet is not # present, the command will return $null. + Write-Verbose -Message ($script:localizedData.GetReplicationSubnet -f $Name) $replicationSubnet = Get-ADReplicationSubnet -Filter { Name -eq $Name } if ($null -eq $replicationSubnet) { # Replication subnet not found, return absent. + Write-Verbose -Message ($script:localizedData.ReplicationSubnetAbsent -f $Name) $returnValue = @{ Ensure = 'Absent' Name = $Name @@ -48,7 +58,8 @@ function Get-TargetResource $replicationSiteName = Get-ADObject -Identity $replicationSubnet.Site | Select-Object -ExpandProperty 'Name' } - # Replication subnet not found, return present. + # Replication subnet found, return present. + Write-Verbose -Message ($script:localizedData.ReplicationSubnetPresent -f $Name) $returnValue = @{ Ensure = 'Present' Name = $Name @@ -110,7 +121,7 @@ function Set-TargetResource # Add the replication subnet, if it does not exist. if ($null -eq $replicationSubnet) { - Write-Verbose "Create the replication subnet $Name" + Write-Verbose -Message ($script:localizedData.CreateReplicationSubnet -f $Name) $replicationSubnet = New-ADReplicationSubnet -Name $Name -Site $Site -PassThru } @@ -123,7 +134,7 @@ function Set-TargetResource } if ($replicationSiteName -ne $Site) { - Write-Verbose "Set on replication subnet $Name the site to $Site" + Write-Verbose -Message ($script:localizedData.SetReplicationSubnetSite -f $Name, $Site) Set-ADReplicationSubnet -Identity $replicationSubnet.DistinguishedName -Site $Site -PassThru } @@ -138,7 +149,7 @@ function Set-TargetResource } if ($replicationSubnet.Location -ne $nullableLocation) { - Write-Verbose "Set on replication subnet $Name the location to $nullableLocation" + Write-Verbose -Message ($script:localizedData.SetReplicationSubnetLocation -f $Name, $nullableLocation) Set-ADReplicationSubnet -Identity $replicationSubnet.DistinguishedName -Location $nullableLocation -PassThru } @@ -149,7 +160,7 @@ function Set-TargetResource # Remove the replication subnet, if it exists. if ($null -ne $replicationSubnet) { - Write-Verbose "Remove the replication subnet $Name" + Write-Verbose -Message ($script:localizedData.RemoveReplicationSubnet -f $Name) Remove-ADReplicationSubnet -Identity $replicationSubnet.DistinguishedName -Confirm:$false } @@ -209,5 +220,14 @@ function Test-TargetResource $currentConfiguration.Location -eq $Location } + if ($desiredConfigurationMatch) + { + Write-Verbose -Message ($script:localizedData.ReplicationSubnetInDesiredState -f $Name) + } + else + { + Write-Verbose -Message ($script:localizedData.ReplicationSubnetNotInDesiredState -f $Name) + } + return $desiredConfigurationMatch } diff --git a/DSCResources/MSFT_xADReplicationSubnet/en-US/MSFT_xADReplicationSubnet.strings.psd1 b/DSCResources/MSFT_xADReplicationSubnet/en-US/MSFT_xADReplicationSubnet.strings.psd1 new file mode 100644 index 000000000..a7dce578c --- /dev/null +++ b/DSCResources/MSFT_xADReplicationSubnet/en-US/MSFT_xADReplicationSubnet.strings.psd1 @@ -0,0 +1,12 @@ +# culture='en-US' +ConvertFrom-StringData @' + CreateReplicationSubnet = Create the replication subnet '{0}'. + RemoveReplicationSubnet = Remove the replication subnet '{0}'. + GetReplicationSubnet = Getting replication subnet '{0}'. + SetReplicationSubnetSite = Set the replication subnet '{0}' site to '{1}'. + SetReplicationSubnetLocation = Set the replication subnet '{0}' location to '{1}'. + ReplicationSubnetAbsent = Replication subnet '{0}' is absent. + ReplicationSubnetPresent = Replication subnet '{0}' is present. + ReplicationSubnetInDesiredState = The replication subnet '{0}' is in the desired state. + ReplicationSubnetNotInDesiredState = The replication subnet '{0}' is not in the desired state. +'@ diff --git a/DSCResources/MSFT_xADServicePrincipalName/MSFT_xADServicePrincipalName.psm1 b/DSCResources/MSFT_xADServicePrincipalName/MSFT_xADServicePrincipalName.psm1 index 773353312..3a596173d 100644 --- a/DSCResources/MSFT_xADServicePrincipalName/MSFT_xADServicePrincipalName.psm1 +++ b/DSCResources/MSFT_xADServicePrincipalName/MSFT_xADServicePrincipalName.psm1 @@ -1,3 +1,10 @@ +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADServicePrincipalName' <# .SYNOPSIS @@ -18,12 +25,14 @@ function Get-TargetResource $ServicePrincipalName ) + Write-Verbose -Message ($script:localizedData.GetServicePrincipalName -f $ServicePrincipalName) $spnAccounts = Get-ADObject -Filter { ServicePrincipalName -eq $ServicePrincipalName } -Properties 'SamAccountName' | Select-Object -ExpandProperty 'SamAccountName' if ($spnAccounts.Count -eq 0) { # No SPN found + Write-Verbose -Message ($script:localizedData.ServicePrincipalNameAbsent -f $ServicePrincipalName) $returnValue = @{ Ensure = 'Absent' ServicePrincipalName = $ServicePrincipalName @@ -33,6 +42,7 @@ function Get-TargetResource else { # One or more SPN(s) found, return the account name(s) + Write-Verbose -Message ($script:localizedData.ServicePrincipalNamePresent -f $ServicePrincipalName, ($spnAccounts -join ';')) $returnValue = @{ Ensure = 'Present' ServicePrincipalName = $ServicePrincipalName @@ -87,7 +97,7 @@ function Set-TargetResource # not exist. if ([String]::IsNullOrEmpty($Account) -or ($null -eq (Get-ADObject -Filter { SamAccountName -eq $Account }))) { - throw "AD object with SamAccountName = '$Account' not found!" + throw ($script:localizedData.AccountNotFound -f $Account) } # Remove the SPN(s) from any extra account. @@ -95,6 +105,7 @@ function Set-TargetResource { if ($spnAccount.SamAccountName -ne $Account) { + Write-Verbose -Message ($script:localizedData.RemoveServicePrincipalName -f $ServicePrincipalName, $spnAccount.SamAccountName) Set-ADObject -Identity $spnAccount.DistinguishedName -Remove @{ ServicePrincipalName = $ServicePrincipalName } } } @@ -104,6 +115,7 @@ function Set-TargetResource # field SamAccountName as Identifier. if ($spnAccounts.SamAccountName -notcontains $Account) { + Write-Verbose -Message ($script:localizedData.AddServicePrincipalName -f $ServicePrincipalName, $Account) Get-ADObject -Filter { SamAccountName -eq $Account } | Set-ADObject -Add @{ ServicePrincipalName = $ServicePrincipalName } } @@ -114,6 +126,7 @@ function Set-TargetResource { foreach ($spnAccount in $spnAccounts) { + Write-Verbose -Message ($script:localizedData.RemoveServicePrincipalName -f $ServicePrincipalName, $spnAccount.SamAccountName) Set-ADObject -Identity $spnAccount.DistinguishedName -Remove @{ ServicePrincipalName = $ServicePrincipalName } } } @@ -165,5 +178,14 @@ function Test-TargetResource $currentConfiguration.Account -eq $Account } + if ($desiredConfigurationMatch) + { + Write-Verbose -Message ($script:localizedData.ServicePrincipalNameInDesiredState -f $ServicePrincipalName) + } + else + { + Write-Verbose -Message ($script:localizedData.ServicePrincipalNameNotInDesiredState -f $ServicePrincipalName) + } + return $desiredConfigurationMatch } diff --git a/DSCResources/MSFT_xADServicePrincipalName/en-US/MSFT_xADServicePrincipalName.strings.psd1 b/DSCResources/MSFT_xADServicePrincipalName/en-US/MSFT_xADServicePrincipalName.strings.psd1 new file mode 100644 index 000000000..75545a922 --- /dev/null +++ b/DSCResources/MSFT_xADServicePrincipalName/en-US/MSFT_xADServicePrincipalName.strings.psd1 @@ -0,0 +1,11 @@ +# culture='en-US' +ConvertFrom-StringData @' + GetServicePrincipalName = Getting service principal name '{0}'. + ServicePrincipalNameAbsent = Service principal name '{0}' is absent. + ServicePrincipalNamePresent = Service principal name '{0}' is present on account(s) '{1}' + AccountNotFound = AD object with SamAccountName '{0}' not found! + RemoveServicePrincipalName = Removing service principal name '{0}' from account '{1}'. + AddServicePrincipalName = Adding service principal name '{0}' to account '{1}. + ServicePrincipalNameInDesiredState = Service principal name '{0}' is in the desired state. + ServicePrincipalNameNotInDesiredState = Service principal name '{0}' is not in the desired state. +'@ diff --git a/DSCResources/MSFT_xWaitForADDomain/MSFT_xWaitForADDomain.psm1 b/DSCResources/MSFT_xWaitForADDomain/MSFT_xWaitForADDomain.psm1 index 9d671d7ab..a14f6ae8c 100644 --- a/DSCResources/MSFT_xWaitForADDomain/MSFT_xWaitForADDomain.psm1 +++ b/DSCResources/MSFT_xWaitForADDomain/MSFT_xWaitForADDomain.psm1 @@ -1,17 +1,29 @@ -function Get-TargetResource +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xWaitForADDomain' + +function Get-TargetResource { [OutputType([System.Collections.Hashtable])] param ( - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$DomainName, + [Parameter()] [PSCredential]$DomainUserCredential, + [Parameter()] [UInt64]$RetryIntervalSec = 60, + [Parameter()] [UInt32]$RetryCount = 10, + [Parameter()] [UInt32]$RebootRetryCount = 0 ) @@ -25,6 +37,7 @@ $convertToCimCredential = $null } + Write-Verbose -Message ($script:localizedData.GetDomain -f $DomainName) $domain = Get-Domain -DomainName $DomainName -DomainUserCredential $DomainUserCredential @@ -55,15 +68,19 @@ function Set-TargetResource [CmdletBinding()] param ( - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$DomainName, + [Parameter()] [PSCredential]$DomainUserCredential, + [Parameter()] [UInt64]$RetryIntervalSec = 60, + [Parameter()] [UInt32]$RetryCount = 10, + [Parameter()] [UInt32]$RebootRetryCount = 0 ) @@ -85,7 +102,7 @@ function Set-TargetResource } else { - Write-Verbose -Message "Domain $DomainName not found. Will retry again after $RetryIntervalSec sec" + Write-Verbose -Message ($script:localizedData.DomainNotFoundRetrying -f $DomainName, $RetryIntervalSec) Start-Sleep -Seconds $RetryIntervalSec Clear-DnsClientCache } @@ -100,20 +117,20 @@ function Set-TargetResource if($rebootCount -lt $RebootRetryCount) { $rebootCount = $rebootCount + 1 - Write-Verbose -Message "Domain $DomainName not found after $count attempts with $RetryIntervalSec sec interval. Rebooting. Reboot attempt number $rebootCount of $RebootRetryCount." + Write-Verbose -Message ($script:localizedData.DomainNotFoundRebooting -f $DomainName, $count, $RetryIntervalSec, $rebootCount, $RebootRetryCount) Set-Content -Path $RebootLogFile -Value $rebootCount $global:DSCMachineStatus = 1 } else { - throw "Domain '$($DomainName)' NOT found after $RebootRetryCount Reboot attempts." + throw ($script:localizedData.DomainNotFoundAfterReboot -f $DomainName, $RebootRetryCount) } } else { - throw "Domain '$($DomainName)' NOT found after $RetryCount attempts." + throw ($script:localizedData.DomainNotFoundAfterRetry -f $DomainName, $RetryCount) } } } @@ -123,15 +140,19 @@ function Test-TargetResource [OutputType([System.Boolean])] param ( - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$DomainName, + [Parameter()] [PSCredential]$DomainUserCredential, + [Parameter()] [UInt64]$RetryIntervalSec = 60, + [Parameter()] [UInt32]$RetryCount = 10, + [Parameter()] [UInt32]$RebootRetryCount = 0 ) @@ -147,10 +168,12 @@ function Test-TargetResource Remove-Item $rebootLogFile -ErrorAction SilentlyContinue } + Write-Verbose -Message ($script:localizedData.DomainInDesiredState -f $DomainName) $true } else { + Write-Verbose -Message ($script:localizedData.DomainNotInDesiredState -f $DomainName) $false } } @@ -162,13 +185,14 @@ function Get-Domain [OutputType([PSObject])] param ( - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [String]$DomainName, + [Parameter()] [PSCredential]$DomainUserCredential ) - Write-Verbose -Message "Checking for domain $DomainName ..." + Write-Verbose -Message ($script:localizedData.CheckDomain -f $DomainName) if($DomainUserCredential) { @@ -182,7 +206,7 @@ function Get-Domain try { $domain = ([System.DirectoryServices.ActiveDirectory.DomainController]::FindOne($context)).domain.ToString() - Write-Verbose -Message "Found domain $DomainName" + Write-Verbose -Message ($script:localizedData.FoundDomain -f $DomainName) $returnValue = @{ Name = $domain } @@ -191,6 +215,6 @@ function Get-Domain } catch { - Write-Verbose -Message "Domain $DomainName not found" + Write-Verbose -Message ($script:localizedData.DomainNotFound -f $DomainName) } } diff --git a/DSCResources/MSFT_xWaitForADDomain/en-US/MSFT_xWaitForADDomain.strings.psd1 b/DSCResources/MSFT_xWaitForADDomain/en-US/MSFT_xWaitForADDomain.strings.psd1 new file mode 100644 index 000000000..61c62401f --- /dev/null +++ b/DSCResources/MSFT_xWaitForADDomain/en-US/MSFT_xWaitForADDomain.strings.psd1 @@ -0,0 +1,13 @@ +# culture='en-US' +ConvertFrom-StringData @' + GetDomain = Getting Domain '{0}'. + DomainNotFoundRetrying = Domain '{0}' not found. Will retry again after {1} seconds. + DomainNotFoundRebooting = Domain '{0}' not found after {1} attempts with {2} sec interval. Rebooting. Reboot attempt number {3} of {4}. + DomainNotFoundAfterReboot = Domain '{0}' NOT found after {1} Reboot attempts. + DomainNotFoundAfterRetry = Domain '{0}' NOT found after {1} attempts. + DomainInDesiredState = Domain '{0}' is in the desired state. + DomainNotInDesiredState = Domain '{0}' is not in the desired state. + CheckDomain = Checking for domain '{0}' ... + FoundDomain = Found domain '{0}'. + DomainNotFound = Domain '{0}' not found. +'@ diff --git a/Tests/Unit/MSFT_xADRecycleBin.Tests.ps1 b/Tests/Unit/MSFT_xADRecycleBin.Tests.ps1 new file mode 100644 index 000000000..ec4a538bd --- /dev/null +++ b/Tests/Unit/MSFT_xADRecycleBin.Tests.ps1 @@ -0,0 +1,220 @@ +$script:DSCModuleName = 'xActiveDirectory' +$script:DSCResourceName = 'MSFT_xADRecycleBin' + +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests')) +} + +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +try +{ + InModuleScope $script:DSCResourceName { + + $forestFQDN = 'contoso.com' + $forestFunctionality = 'Windows2016Forest' + $configurationNamingContext = 'CN=Configuration,DC=contoso,DC=com' + $testCredential = [System.Management.Automation.PSCredential]::Empty + + $mockRootDSE = @{ + configurationNamingContext = $configurationNamingContext + forestFunctionality = $forestFunctionality + } + + $mockADObjectNoRecycleBin = New-MockObject -Type Microsoft.ActiveDirectory.Management.ADObject + $mockADObjectNoRecycleBin.'msDS-EnabledFeature' = @('') + + $mockADObjectRecycleBin = New-MockObject -Type Microsoft.ActiveDirectory.Management.ADObject + $mockADObjectRecycleBin.'msDS-EnabledFeature' = @( + "CN=Recycle Bin Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,$($configurationNamingContext)" + ) + + $targetResourceParameters = @{ + ForestFQDN = $ForestFQDN + EnterpriseAdministratorCredential = $testCredential + } + + $mockGetTargetResourceReturnValueRecycleBinEnabled = @{ + ForestFQDN = $forestFQDN + RecycleBinEnabled = $true + ForestMode = $forestFunctionality + } + + $mockGetTargetResourceReturnValueRecycleBinNotEnabled = @{ + ForestFQDN = $forestFQDN + RecycleBinEnabled = $false + ForestMode = $forestFunctionality + } + + $mockADForestLevel3 = @{ + ForestMode = 3 + RootDomain = $forestFQDN + DomainNamingMaster = "dc01.$forestFQDN" + } + + $mockADForestLevel4 = @{ + ForestMode = 4 + RootDomain = $forestFQDN + DomainNamingMaster = "dc01.$forestFQDN" + } + + Describe 'MSFT_xADRecycleBin\Get-TargetResource' { + Mock -CommandName Get-ADRootDSE -MockWith { $mockRootDSE } + + Context 'When Recycle Bin feature is installed' { + Mock -CommandName Get-ADObject -MockWith { $mockADObjectRecycleBin } + + It 'Should return expected properties' { + $targetResource = Get-TargetResource @targetResourceParameters + + $targetResource.ForestFQDN | Should -Be $mockGetTargetResourceReturnValueRecycleBinEnabled.ForestFQDN + $targetResource.RecycleBinEnabled | Should -Be $mockGetTargetResourceReturnValueRecycleBinEnabled.RecycleBinEnabled + $targetResource.ForestMode | Should -Be $mockGetTargetResourceReturnValueRecycleBinEnabled.ForestMode + } + } + + Context 'When Recycle Bin feature not installed' { + Mock -CommandName Get-ADObject -MockWith { $mockADObjectNoRecycleBin } + + It 'Should return expected properties' { + $targetResource = Get-TargetResource @targetResourceParameters + + $targetResource.ForestFQDN | Should -Be $mockGetTargetResourceReturnValueRecycleBinNotEnabled.ForestFQDN + $targetResource.RecycleBinEnabled | Should -Be $mockGetTargetResourceReturnValueRecycleBinNotEnabled.RecycleBinEnabled + $targetResource.ForestMode | Should -Be $mockGetTargetResourceReturnValueRecycleBinNotEnabled.ForestMode + } + } + + Context 'When Get-AdObject throws an exception' { + Mock -CommandName Write-Error + + It 'Should throw ADIdentityNotFoundException' { + Mock -CommandName Get-ADObject -MockWith { Throw (New-Object -TypeName Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException) } + { Get-TargetResource @targetResourceParameters } | Should -Throw Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException + } + + It 'Should throw ADServerDownException' { + Mock -CommandName Get-ADObject -MockWith { Throw (New-Object -TypeName Microsoft.ActiveDirectory.Management.ADServerDownException) } + { Get-TargetResource @targetResourceParameters } | Should -Throw Microsoft.ActiveDirectory.Management.ADServerDownException + } + + It 'Should throw AuthenticationException' { + Mock -CommandName Get-ADObject -MockWith { Throw (New-Object -TypeName System.Security.Authentication.AuthenticationException) } + { Get-TargetResource @targetResourceParameters } | Should -Throw 'System error' + } + + It 'Should throw UnhandledException' { + Mock -CommandName Get-ADObject -MockWith { Throw Unhandled.Exception } + { Get-TargetResource @targetResourceParameters } | Should -Throw Unhandled.Exception + } + } + } + + Describe 'MSFT_xADRecycleBin\Test-TargetResource' { + Mock -CommandName Get-ADRootDSE -MockWith { $mockRootDSE } + + Context 'When Recycle Bin feature is installed' { + Mock -CommandName Get-ADObject -MockWith { $mockADObjectRecycleBin } + + It 'Should return true' { + Test-TargetResource @targetResourceParameters | Should -Be $true + } + } + + Context 'When Recycle Bin feature not installed' { + Mock -CommandName Get-ADObject -MockWith { $mockADObjectNoRecycleBin } + + It 'Should return false' { + Test-TargetResource @targetResourceParameters | Should -Be $false + } + } + + Context 'When Get-AdObject throws an exception' { + Mock -CommandName Write-Error + + It 'Should throw ADIdentityNotFoundException' { + Mock -CommandName Get-ADObject -MockWith { Throw (New-Object -TypeName Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException) } + { Test-TargetResource @targetResourceParameters } | Should -Throw Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException + } + + It 'Should throw ADServerDownException' { + Mock -CommandName Get-ADObject -MockWith { Throw (New-Object -TypeName Microsoft.ActiveDirectory.Management.ADServerDownException) } + { Test-TargetResource @targetResourceParameters } | Should -Throw Microsoft.ActiveDirectory.Management.ADServerDownException + } + + It 'Should throw AuthenticationException' { + Mock -CommandName Get-ADObject -MockWith { Throw (New-Object -TypeName System.Security.Authentication.AuthenticationException) } + { Test-TargetResource @targetResourceParameters } | Should -Throw 'System error' + } + + It 'Should throw UnhandledException' { + Mock -CommandName Get-ADObject -MockWith { Throw Unhandled.Exception } + { Test-TargetResource @targetResourceParameters } | Should -Throw Unhandled.Exception + } + } + } + + Describe 'MSFT_xADRecycleBin\Set-TargetResource' { + Mock -CommandName Enable-ADOptionalFeature -MockWith { } + + Context 'When minimum forest level is too low' { + Mock -CommandName Get-ADForest -MockWith { $mockADForestLevel3 } + It 'Should Throw' { + { Set-TargetResource @targetResourceParameters } | Should -Throw + } + + It 'Should not call Enable-ADOptionalFeature' { + Assert-MockCalled Enable-ADOptionalFeature -Scope It -Times 0 -Exactly + } + } + + Context 'When minimum forest level is met' { + Mock -CommandName Get-ADForest -MockWith { $mockADForestLevel4 } + It 'Should not Throw' { + { Set-TargetResource @targetResourceParameters } | Should -Not -Throw + } + + It 'Should call Enable-ADOptionalFeature' { + Set-TargetResource @targetResourceParameters + + Assert-MockCalled Enable-ADOptionalFeature -Scope It -Times 1 -Exactly + } + } + + Context 'When Get-AdForest throws an exception' { + Mock -CommandName Write-Error + + It 'Should throw ADIdentityNotFoundException' { + Mock -CommandName Get-ADForest -MockWith { Throw (New-Object -TypeName Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException) } + { Set-TargetResource @targetResourceParameters } | Should -Throw Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException + } + + It 'Should throw ADServerDownException' { + Mock -CommandName Get-ADForest -MockWith { Throw (New-Object -TypeName Microsoft.ActiveDirectory.Management.ADServerDownException) } + { Set-TargetResource @targetResourceParameters } | Should -Throw Microsoft.ActiveDirectory.Management.ADServerDownException + } + + It 'Should throw AuthenticationException' { + Mock -CommandName Get-ADForest -MockWith { Throw (New-Object -TypeName System.Security.Authentication.AuthenticationException) } + { Set-TargetResource @targetResourceParameters } | Should -Throw 'System error' + } + + It 'Should throw UnhandledException' { + Mock -CommandName Get-ADForest -MockWith { Throw Unhandled.Exception } + { Set-TargetResource @targetResourceParameters } | Should -Throw Unhandled.Exception + } + } + } + } +} +finally +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} diff --git a/Tests/Unit/MSFT_xADServicePrincipalName.Tests.ps1 b/Tests/Unit/MSFT_xADServicePrincipalName.Tests.ps1 index 31685e0f4..4d1937d26 100644 --- a/Tests/Unit/MSFT_xADServicePrincipalName.Tests.ps1 +++ b/Tests/Unit/MSFT_xADServicePrincipalName.Tests.ps1 @@ -188,7 +188,7 @@ try It 'Should throw the correct exception' { - { Set-TargetResource @testPresentParams } | Should Throw "AD object with SamAccountName = 'User' not found!" + { Set-TargetResource @testPresentParams } | Should Throw "AD object with SamAccountName 'User' not found!" } }