diff --git a/.MetaTestOptIn.json b/.MetaTestOptIn.json index 8a15ee5..8955237 100644 --- a/.MetaTestOptIn.json +++ b/.MetaTestOptIn.json @@ -1,4 +1,6 @@ [ "Common Tests - Validate Example Files", - "Common Tests - Validate Markdown Files" + "Common Tests - Validate Markdown Files", + "Common Tests - Validate Module Files", + "Common Tests - Validate Script Files" ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 24f9454..9a90e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,49 +2,121 @@ ## Unreleased +## 1.8.0.0 + +- Changes to xFailOverCluster + - Added a common resource helper module with helper functions for localization. + - Added helper functions; Get-LocalizedData, New-InvalidResultException, + New-ObjectNotFoundException, New-InvalidOperationException and + New-InvalidArgumentException. + - Fixed lint error MD034 and fixed typos in README.md. + - Opt-in for module files common tests ([issue #119](https://github.com/PowerShell/xFailOverCluster/issues/119)). + - Removed Byte Order Mark (BOM) from the files; CommonResourceHelper.psm1 and FailoverClusters.stubs.psm1. + - Opt-in for script files common tests ([issue #121](https://github.com/PowerShell/xFailOverCluster/issues/121)). + - Removed Byte Order Mark (BOM) from the files; CommonResourceHelper.Tests.ps1, + MSFT\_xCluster.Tests.ps1, MSFT\_xClusterDisk.Tests.ps1, + MSFT\_xClusterPreferredOwner.Tests.ps1, MSFT_xWaitForCluster.Tests.ps1. + - Added common test helper functions to help test the throwing of localized error strings. + - Get-InvalidArgumentRecord + - Get-InvalidOperationRecord + - Get-ObjectNotFoundException + - Get-InvalidResultException. + - Updated year to 2017 in license file and module manifest ([issue #131](https://github.com/PowerShell/xFailOverCluster/issues/131)). +- Changes to xClusterDisk + - Enabled localization for all strings ([issue #84](https://github.com/PowerShell/xFailOverCluster/issues/84)). + - Fixed the OutputType data type that was not fully qualified. + - Minor style changes. + - Fixed Script Analyzer warnings for Write-Verbose. +- Changes to xClusterNetwork + - Replaced the URL for the parameter Role in README.md. The new URL is a more + generic description of the possible settings for the Role parameter. The + previous URL was still correct but focused on Hyper-V in particular. + - Fixed typos in parameter descriptions in README.md, comment-based help and schema.mof. + - Enabled localization for all strings ([issue #85](https://github.com/PowerShell/xFailOverCluster/issues/85)). + - Minor style changes. + - Fixed Script Analyzer warnings for Write-Verbose. +- Changes to xCluster + - Resolved Script Analyzer rule warnings by changing Get-WmiObject to + Get-CimInstance ([issue #49](https://github.com/PowerShell/xFailOverCluster/issues/49)). + - Minor style change in tests. Removed '-' in front of '-Be', '-Not', '-Throw', + etc. + - Enabled localization for all strings ([issue #83](https://github.com/PowerShell/xFailOverCluster/issues/83)). + - Added tests to improve code coverage. + - Fixed random problem with tests failing with error "Invalid token for + impersonation - it cannot be duplicated." ([issue #133](https://github.com/PowerShell/xFailOverCluster/issues/133)). + - Minor style changes. + - Fixed Script Analyzer warnings for Write-Verbose. +- Changes to xWaitForCluster + - Refactored the unit test for this resource to use stubs and increase coverage + ([issue #78](https://github.com/PowerShell/xFailOverCluster/issues/78)). + - Now the Test-TargetResource correctly returns false if the domain name cannot + be evaluated ([issue #107](https://github.com/PowerShell/xFailOverCluster/issues/107)). + - Changed the code to be more aligned with the style guideline. + - Updated parameter description in the schema.mof. + - Resolved Script Analyzer warnings ([issue #54](https://github.com/PowerShell/xFailOverCluster/issues/54)). + - Enabled localization for all strings ([issue #88](https://github.com/PowerShell/xFailOverCluster/issues/88)). + - Minor style changes. +- Changes to xClusterQuorum + - Refactored the unit test for this resource to use stubs and increase coverage + ([issue #77](https://github.com/PowerShell/xFailOverCluster/issues/77)). + - Changed the code to be more aligned with the style guideline. + - Updated parameter description in the schema.mof. + - Added example ([issue #47](https://github.com/PowerShell/xFailOverCluster/issues/47)) + - 1-SetQuorumToNodeMajority.ps1 + - 2-SetQuorumToNodeAndDiskMajority.ps1 + - 3-SetQuorumToNodeAndFileShareMajority.ps1 + - 4-SetQuorumToDiskOnly.ps1 + - Added links to examples from README.md. + - Minor style changes. + - Enabled localization for all strings ([issue #87](https://github.com/PowerShell/xFailOverCluster/issues/87)). +- Changes to xClusterPreferredOwner + - Enabled localization for all strings ([issue #86](https://github.com/PowerShell/xFailOverCluster/issues/86)). + - Fixed typo in the returned hash table from Get-TargetResource. + - Minor style changes. + ## 1.7.0.0 - Changes to xClusterPreferredOwner - - Script Analyzer warnings have been fixed (issue #50). This also failed the + - Script Analyzer warnings have been fixed ([issue #50](https://github.com/PowerShell/xFailOverCluster/issues/50)). This also failed the tests for the resource. - Changes to xClusterDisk - - Fixed test that was failing in AppVeyor (issue #55). + - Fixed test that was failing in AppVeyor ([issue #55](https://github.com/PowerShell/xFailOverCluster/issues/55)). - Changes to xFailOverCluster - - Added 'Code of Conduct' text to the README.md (issue #44). - - Added TOC for all resources in the README.md (issue #43). + - Added 'Code of Conduct' text to the README.md ([issue #44](https://github.com/PowerShell/xFailOverCluster/issues/44)). + - Added TOC for all resources in the README.md ([issue #43](https://github.com/PowerShell/xFailOverCluster/issues/43)). - Fixed typos and lint errors in README.md. - Fixed style issue in example in README.md. - Removed 'Unreleased' "tag" from the resources xClusterQuorum and - xClusterDisk (issue #36) + xClusterDisk ([issue #36](https://github.com/PowerShell/xFailOverCluster/issues/36)) - Added new sections to each resource (Requirements, Parameters and Examples) in the README.md. Some does not yet have any examples, so they are set to 'None.'. - Added GitHub templates PULL\_REQUEST\_TEMPLATE, ISSUE_TEMPLATE and - CONTRIBUTING.md (issue #45). + CONTRIBUTING.md ([issue #45](https://github.com/PowerShell/xFailOverCluster/issues/45)). - Split the change log from README.md to a separate file CHANGELOG.md - (issue #48). - - Added the resource xClusterPreferredOwner to README.md (issue #51). - - Added the resource xClusterNetwork to README.md (issue #56). + [issue #48](https://github.com/PowerShell/xFailOverCluster/issues/48). + - Added the resource xClusterPreferredOwner to README.md ([issue #51](https://github.com/PowerShell/xFailOverCluster/issues/51)). + - Added the resource xClusterNetwork to README.md ([issue #56](https://github.com/PowerShell/xFailOverCluster/issues/56)). - Removed Credential parameter from parameter list for xWaitForCluster. Parameter Credential does not exist in the schema.mof of the resource - (issue #62). + ([issue #62](https://github.com/PowerShell/xFailOverCluster/issues/62)). - Now all parameters in the README.md list their data type and type qualifier - (issue #58.) + ([issue #58](https://github.com/PowerShell/xFailOverCluster/issues/58)). - Added Import-DscResource to example in README.md. - - Added CodeCov and opt-in for all common tests (issue #41). + - Added CodeCov and opt-in for all common tests ([issue #41](https://github.com/PowerShell/xFailOverCluster/issues/41)). - Added CodeCov badge to README.md - Fixed CodeCov badge links so they now can be clicked on. - Fixed lint rule MD013 in CHANGELOG.md. - Fixed lint rule MD013 in README.md. - Fixed lint rule MD024 in README.md. - Fixed lint rule MD032 in README.md. - - Removed example from README.md (issue #42). + - Removed example from README.md ([issue #42](https://github.com/PowerShell/xFailOverCluster/issues/42)). - Fixed typo in filename for ISSUE\_TEMPLATE. Was 'ISSUE\_TEMPLATE', now it is correctly 'ISSUE\_TEMPLATE.md'. - Changed appveyor.yml to use the new default test framework in the AppVeyor module in DscResource.Tests (AppVeyor.psm1). - Added VS Code workspace settings file with formatting settings matching the - Style Guideline (issue #67). That will make it possible inside VS Code to + Style Guideline ([issue #67](https://github.com/PowerShell/xFailOverCluster/issues/67)). That will make it possible inside VS Code to press SHIFT+ALT+F, or press F1 and choose 'Format document' in the list. The PowerShell code will then be formatted according to the Style Guideline (although maybe not complete, but would help a long way). @@ -63,41 +135,41 @@ - Fixed typo in xCluster parameter description. - Added links to examples from README.md - Refactored the unit test for this resource to use stubs and increase coverage - (issue #73). + ([issue #73](https://github.com/PowerShell/xFailOverCluster/issues/73)). - Removed the password file (MSFT_xCluster.password.txt) which seemed unnecessary. - Test-TargetResource now throws and error if domain name cannot be evaluated - (issue #72). + ([issue #72](https://github.com/PowerShell/xFailOverCluster/issues/72)). - Set-TargetResource now correctly throws and error if domain name cannot be - evaluated (issue #71). + evaluated ([issue #71](https://github.com/PowerShell/xFailOverCluster/issues/71)). - Changes to xWaitForCluster - Added example - 1-WaitForFailoverClusterToBePresent.ps1 - Added link to example from README.md - Changes to xClusterDisk - Refactored the unit test for this resource to use stubs and increase coverage - (issue #74). + ([issue #74](https://github.com/PowerShell/xFailOverCluster/issues/74)). - Removed an evaluation that called Test-TargetResource in Set-TargetResource method and instead added logic so that Set-TargetResource evaluates if it - should remove a disk (issue #90). + should remove a disk ([issue #90](https://github.com/PowerShell/xFailOverCluster/issues/90)). - Changed the code to be more aligned with the style guideline. - - Added examples (issue #46) + - Added examples ([issue #46](https://github.com/PowerShell/xFailOverCluster/issues/46)) - 1-AddClusterDisk.ps1 - 2-RemoveClusterDisk.ps1 - Added links to examples from README.md. - Changes to xClusterPreferredOwner - Refactored the unit test for this resource to use stubs and increase coverage - (issue #76). + ([issue #76](https://github.com/PowerShell/xFailOverCluster/issues/76)). - Changed the code to be more aligned with the style guideline. - - Added examples (issue #52) + - Added examples ([issue #52](https://github.com/PowerShell/xFailOverCluster/issues/52)) - 1-AddPreferredOwner.ps1 - 2-RemovePreferredOwner.ps1 - Added links to examples from README.md. - Changes to xClusterNetwork - Refactored the unit test for this resource to use stubs and increase coverage - (issue #75). + ([issue #75](https://github.com/PowerShell/xFailOverCluster/issues/75)). - Changed the code to be more aligned with the style guideline. - Updated resource and parameter description in README.md and schema.mof. - - Added example (issue #57) + - Added example ([issue #57](https://github.com/PowerShell/xFailOverCluster/issues/57)) - 1-ChangeClusterNetwork.ps1 - Added links to examples from README.md. diff --git a/DSCResources/CommonResourceHelper.psm1 b/DSCResources/CommonResourceHelper.psm1 new file mode 100644 index 0000000..61e4f76 --- /dev/null +++ b/DSCResources/CommonResourceHelper.psm1 @@ -0,0 +1,265 @@ +<# + .SYNOPSIS + Creates and throws an invalid argument exception. + + .PARAMETER Message + The message explaining why this error is being thrown. + + .PARAMETER ArgumentName + The name of the invalid argument that is causing this error to be thrown. +#> +function New-InvalidArgumentException +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ArgumentName + ) + + $argumentException = New-Object -TypeName 'ArgumentException' ` + -ArgumentList @($Message, $ArgumentName) + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @($argumentException, $ArgumentName, 'InvalidArgument', $null) + } + + $errorRecord = New-Object @newObjectParameters + + throw $errorRecord +} + +<# + .SYNOPSIS + Creates and throws an invalid operation exception. + + .PARAMETER Message + The message explaining why this error is being thrown. + + .PARAMETER ErrorRecord + The error record containing the exception that is causing this terminating error. +#> +function New-InvalidOperationException +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter()] + [ValidateNotNull()] + [System.Management.Automation.ErrorRecord] + $ErrorRecord + ) + + if ($null -eq $ErrorRecord) + { + $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` + -ArgumentList @($Message) + } + else + { + $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` + -ArgumentList @($Message, $ErrorRecord.Exception) + } + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( + $invalidOperationException.ToString(), + 'MachineStateIncorrect', + 'InvalidOperation', + $null + ) + } + + $errorRecordToThrow = New-Object @newObjectParameters + + throw $errorRecordToThrow +} + +<# + .SYNOPSIS + Creates and throws an object not found exception. + + .PARAMETER Message + The message explaining why this error is being thrown. + + .PARAMETER ErrorRecord + The error record containing the exception that is causing this terminating error. +#> +function New-ObjectNotFoundException +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter()] + [ValidateNotNull()] + [System.Management.Automation.ErrorRecord] + $ErrorRecord + ) + + if ($null -eq $ErrorRecord) + { + $exception = New-Object -TypeName 'System.Exception' ` + -ArgumentList @($Message) + } + else + { + $exception = New-Object -TypeName 'System.Exception' ` + -ArgumentList @($Message, $ErrorRecord.Exception) + } + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( + $exception.ToString(), + 'MachineStateIncorrect', + 'ObjectNotFound', + $null + ) + } + + $errorRecordToThrow = New-Object @newObjectParameters + + throw $errorRecordToThrow +} + +<# + .SYNOPSIS + Creates and throws an invalid result exception. + + .PARAMETER Message + The message explaining why this error is being thrown. + + .PARAMETER ErrorRecord + The error record containing the exception that is causing this terminating error. +#> +function New-InvalidResultException +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter()] + [ValidateNotNull()] + [System.Management.Automation.ErrorRecord] + $ErrorRecord + ) + + if ($null -eq $ErrorRecord) + { + $exception = New-Object -TypeName 'System.Exception' ` + -ArgumentList @($Message) + } + else + { + $exception = New-Object -TypeName 'System.Exception' ` + -ArgumentList @($Message, $ErrorRecord.Exception) + } + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( + $exception.ToString(), + 'MachineStateIncorrect', + 'InvalidResult', + $null + ) + } + + $errorRecordToThrow = New-Object @newObjectParameters + + throw $errorRecordToThrow +} + +<# + .SYNOPSIS + Retrieves the localized string data based on the machine's culture. + Falls back to en-US strings if the machine's culture is not supported. + + .PARAMETER ResourceName + The name of the resource as it appears before '.strings.psd1' of the localized string file. + For example: + For WindowsOptionalFeature: MSFT_WindowsOptionalFeature + For Service: MSFT_ServiceResource + For Registry: MSFT_RegistryResource + For Helper: xSQLServerHelper + + .PARAMETER ScriptRoot + Optional. The root path where to expect to find the culture folder. This is only needed + for localization in helper modules. This should not normally be used for resources. +#> +function Get-LocalizedData +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ResourceName, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ScriptRoot + ) + + if ( -not $ScriptRoot ) + { + $resourceDirectory = Join-Path -Path $PSScriptRoot -ChildPath $ResourceName + $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath $PSUICulture + } + else + { + $localizedStringFileLocation = Join-Path -Path $ScriptRoot -ChildPath $PSUICulture + } + + if (-not (Test-Path -Path $localizedStringFileLocation)) + { + # Fallback to en-US + if ( -not $ScriptRoot ) + { + $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath 'en-US' + } + else + { + $localizedStringFileLocation = Join-Path -Path $ScriptRoot -ChildPath 'en-US' + } + } + + Import-LocalizedData ` + -BindingVariable 'localizedData' ` + -FileName "$ResourceName.strings.psd1" ` + -BaseDirectory $localizedStringFileLocation + + return $localizedData +} + +Export-ModuleMember -Function @( + 'New-InvalidArgumentException', + 'New-InvalidOperationException', + 'New-ObjectNotFoundException', + 'New-InvalidResultException', + 'Get-LocalizedData' ) diff --git a/DSCResources/MSFT_xCluster/MSFT_xCluster.psm1 b/DSCResources/MSFT_xCluster/MSFT_xCluster.psm1 index abf5502..66494ae 100644 --- a/DSCResources/MSFT_xCluster/MSFT_xCluster.psm1 +++ b/DSCResources/MSFT_xCluster/MSFT_xCluster.psm1 @@ -1,3 +1,8 @@ +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonResourceHelper.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xCluster' + <# .SYNOPSIS Returns the current state of the failover cluster. @@ -29,10 +34,13 @@ function Get-TargetResource $DomainAdministratorCredential ) - $computerInformation = Get-WmiObject -Class Win32_ComputerSystem + Write-Verbose -Message ($script:localizedData.GetClusterInformation -f $Name) + + $computerInformation = Get-CimInstance -ClassName Win32_ComputerSystem if (($null -eq $computerInformation) -or ($null -eq $computerInformation.Domain)) { - throw 'Can''t find machine''s domain name' + $errorMessage = $script:localizedData.TargetNodeDomainMissing + New-InvalidOperationException -Message $errorMessage } try @@ -42,7 +50,8 @@ function Get-TargetResource $cluster = Get-Cluster -Name $Name -Domain $computerInformation.Domain if ($null -eq $cluster) { - throw "Can't find the cluster $Name" + $errorMessage = $script:localizedData.ClusterNameNotFound -f $Name + New-ObjectNotFoundException -Message $errorMessage } $address = Get-ClusterGroup -Cluster $Name -Name 'Cluster IP Address' | Get-ClusterParameter -Name 'Address' @@ -105,12 +114,13 @@ function Set-TargetResource $bCreate = $true - Write-Verbose -Message "Checking if Cluster $Name is present ..." + Write-Verbose -Message ($script:localizedData.CheckClusterPresent -f $Name) - $computerInformation = Get-WmiObject -Class Win32_ComputerSystem + $computerInformation = Get-CimInstance -ClassName Win32_ComputerSystem if (($null -eq $computerInformation) -or ($null -eq $computerInformation.Domain)) { - throw 'Can''t find machine''s domain name' + $errorMessage = $script:localizedData.TargetNodeDomainMissing + New-InvalidOperationException -Message $errorMessage } try @@ -133,40 +143,41 @@ function Set-TargetResource if ($bCreate) { - Write-Verbose -Message "Cluster $Name is NOT present" + Write-Verbose -Message ($script:localizedData.ClusterAbsent -f $Name) New-Cluster -Name $Name -Node $env:COMPUTERNAME -StaticAddress $StaticIPAddress -NoStorage -Force -ErrorAction Stop if ( -not (Get-Cluster)) { - throw 'Cluster creation failed. Please verify output of ''Get-Cluster'' command' + $errorMessage = $script:localizedData.FailedCreatingCluster + New-InvalidOperationException -Message $errorMessage } - Write-Verbose -Message "Created Cluster $Name" + Write-Verbose -Message ($script:localizedData.ClusterCreated -f $Name) } else { - Write-Verbose -Message "Add node to Cluster $Name ..." + $targetNodeName = $env:COMPUTERNAME - Write-Verbose -Message "Add-ClusterNode $env:COMPUTERNAME to cluster $Name" + Write-Verbose -Message ($script:localizedData.AddNodeToCluster -f $targetNodeName, $Name) $list = Get-ClusterNode -Cluster $Name foreach ($node in $list) { - if ($node.Name -eq $env:COMPUTERNAME) + if ($node.Name -eq $targetNodeName) { if ($node.State -eq 'Down') { - Write-Verbose -Message "Node $env:COMPUTERNAME was down, need remove it from the list." + Write-Verbose -Message ($script:localizedData.RemoveOfflineNodeFromCluster -f $targetNodeName, $Name) - Remove-ClusterNode -Name $env:COMPUTERNAME -Cluster $Name -Force + Remove-ClusterNode -Name $targetNodeName -Cluster $Name -Force } } } - Add-ClusterNode -Name $env:COMPUTERNAME -Cluster $Name -NoStorage + Add-ClusterNode -Name $targetNodeName -Cluster $Name -NoStorage - Write-Verbose -Message "Added node to Cluster $Name" + Write-Verbose -Message ($script:localizedData.AddNodeToClusterSuccessful -f $targetNodeName, $Name) } } finally @@ -226,12 +237,13 @@ function Test-TargetResource $returnValue = $false - Write-Verbose -Message "Checking if Cluster $Name is present ..." + Write-Verbose -Message ($script:localizedData.CheckClusterPresent -f $Name) - $ComputerInfo = Get-WmiObject -Class Win32_ComputerSystem + $ComputerInfo = Get-CimInstance -ClassName Win32_ComputerSystem if (($null -eq $ComputerInfo) -or ($null -eq $ComputerInfo.Domain)) { - throw "Can't find machine's domain name" + $errorMessage = $script:localizedData.TargetNodeDomainMissing + New-InvalidOperationException -Message $errorMessage } try @@ -240,17 +252,19 @@ function Test-TargetResource $cluster = Get-Cluster -Name $Name -Domain $ComputerInfo.Domain - Write-Verbose -Message "Cluster $Name is present" + Write-Verbose -Message ($script:localizedData.ClusterPresent -f $Name) if ($cluster) { - Write-Verbose -Message "Checking if the node is in cluster $Name ..." + $targetNodeName = $env:COMPUTERNAME + + Write-Verbose -Message ($script:localizedData.CheckClusterNodeIsUp -f $targetNodeName, $Name) $allNodes = Get-ClusterNode -Cluster $Name foreach ($node in $allNodes) { - if ($node.Name -eq $env:COMPUTERNAME) + if ($node.Name -eq $targetNodeName) { if ($node.State -eq 'Up') { @@ -258,7 +272,7 @@ function Test-TargetResource } else { - Write-Verbose -Message "Node is in cluster $Name but is NOT up, treat as NOT in cluster." + Write-Verbose -Message ($script:localizedData.ClusterNodeIsDown -f $targetNodeName, $Name) } break @@ -267,17 +281,17 @@ function Test-TargetResource if ($returnValue) { - Write-Verbose -Message "Node is in cluster $Name" + Write-Verbose -Message ($script:localizedData.ClusterNodePresent -f $targetNodeName, $Name) } else { - Write-Verbose -Message "Node is NOT in cluster $Name" + Write-Verbose -Message ($script:localizedData.ClusterNodeAbsent -f $targetNodeName, $Name) } } } catch { - Write-Verbose -Message "Cluster $Name is NOT present with Error $_.Message" + Write-Verbose -Message ($script:localizedData.ClusterAbsentWithError -f $Name, $_.Message) } finally { @@ -356,12 +370,13 @@ function Set-ImpersonateAs if ($bLogin) { - $Identity = New-Object Security.Principal.WindowsIdentity $userToken + $Identity = New-Object -TypeName Security.Principal.WindowsIdentity -ArgumentList $userToken $context = $Identity.Impersonate() } else { - throw "Can't Logon as User $($Credential.GetNetworkCredential().UserName)." + $errorMessage = $script:localizedData.UnableToImpersonateUser -f $Credential.GetNetworkCredential().UserName + New-InvalidOperationException -Message $errorMessage } $context, $userToken @@ -392,6 +407,7 @@ function Close-UserToken $bLogin = $ImpersonateLib::CloseHandle($Token) if (-not $bLogin) { - throw 'Can''t close token' + $errorMessage = $script:localizedData.UnableToCloseToken -f $Token.ToString() + New-InvalidOperationException -Message $errorMessage } } diff --git a/DSCResources/MSFT_xCluster/en-US/MSFT_xCluster.strings.psd1 b/DSCResources/MSFT_xCluster/en-US/MSFT_xCluster.strings.psd1 new file mode 100644 index 0000000..353c2d4 --- /dev/null +++ b/DSCResources/MSFT_xCluster/en-US/MSFT_xCluster.strings.psd1 @@ -0,0 +1,22 @@ +# Localized resources for xCluster + +ConvertFrom-StringData @' + CheckClusterPresent = Checking if cluster {0} is present. + ClusterPresent = Cluster {0} is present. + ClusterAbsent = Cluster {0} is NOT present. + ClusterCreated = Created cluster {0}. + AddNodeToCluster = Adding node {0} to cluster {1}. + RemoveOfflineNodeFromCluster = Node {0} is down, need to remove it from the cluster {1}. + AddNodeToClusterSuccessful = Added node {0} to cluster {1}. + CheckClusterNodeIsUp = Checking if the node {0} is a member of the cluster {1}, and so that node status is 'Up'. + ClusterNodeIsDown = Node {0} is in the cluster {1} but the status is not 'Up'. Node will be treated as NOT being a member of the cluster {1}. + ClusterNodePresent = Cluster node {0} is a member of cluster {1}. + ClusterNodeAbsent = Cluster node {0} is NOT a member of cluster {1}. + ClusterAbsentWithError = Cluster {0} is NOT present with error: {1} + TargetNodeDomainMissing = Can't find the target node's domain name. + ClusterNameNotFound = Can't find the cluster {0}. + FailedCreatingCluster = Cluster creation failed. Please verify output of 'Get-Cluster' command. + UnableToImpersonateUser = Can't logon as user {0}. + UnableToCloseToken = Can't close impersonation token {0}. + GetClusterInformation = Retrieving information for cluster {0}. +'@ diff --git a/DSCResources/MSFT_xClusterDisk/MSFT_xClusterDisk.psm1 b/DSCResources/MSFT_xClusterDisk/MSFT_xClusterDisk.psm1 index 22b5793..0cfcd56 100644 --- a/DSCResources/MSFT_xClusterDisk/MSFT_xClusterDisk.psm1 +++ b/DSCResources/MSFT_xClusterDisk/MSFT_xClusterDisk.psm1 @@ -1,3 +1,8 @@ +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonResourceHelper.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xClusterDisk' + <# .SYNOPSIS Returns the current state of the failover cluster disk resource. @@ -8,7 +13,7 @@ function Get-TargetResource { [CmdletBinding()] - [OutputType([Hashtable])] + [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] @@ -16,6 +21,8 @@ function Get-TargetResource $Number ) + Write-Verbose -Message ($script:localizedData.GetClusterDiskInformation -f $Number) + if ($null -ne ($diskInstance = Get-CimInstance -ClassName MSCluster_Disk -Namespace 'Root\MSCluster' -Filter "Number = $Number")) { $diskResource = Get-ClusterResource | @@ -78,20 +85,24 @@ function Set-TargetResource { if ($getTargetResourceResult.Ensure -ne $Ensure) { - Write-Verbose "Add the disk $Number to the cluster" + Write-Verbose -Message ($script:localizedData.AddDiskToCluster -f $Number) - Get-ClusterAvailableDisk | Where-Object -FilterScript { $_.Number -eq $Number } | Add-ClusterDisk + Get-ClusterAvailableDisk | Where-Object -FilterScript { + $_.Number -eq $Number + } | Add-ClusterDisk } if ($getTargetResourceResult.Label -ne $Label) { - Write-Verbose "Set the disk $Number label to '$Label'" + Write-Verbose -Message ($script:localizedData.SetDiskLabel -f $Number, $Label) $diskInstance = Get-CimInstance -ClassName MSCluster_Disk -Namespace 'Root\MSCluster' -Filter "Number = $Number" $diskResource = Get-ClusterResource | - Where-Object -FilterScript { $_.ResourceType -eq 'Physical Disk' } | - Where-Object -FilterScript { ($_ | Get-ClusterParameter -Name DiskIdGuid).Value -eq $diskInstance.Id } + Where-Object -FilterScript { $_.ResourceType -eq 'Physical Disk' } | + Where-Object -FilterScript { + ($_ | Get-ClusterParameter -Name DiskIdGuid).Value -eq $diskInstance.Id + } # Set the label of the cluster disk $diskResource.Name = $Label @@ -102,13 +113,15 @@ function Set-TargetResource { if ($getTargetResourceResult.Ensure -eq 'Present' -and $Ensure -eq 'Absent') { - Write-Verbose "Remove the disk $Number from the cluster" + Write-Verbose -Message ($script:localizedData.RemoveDiskFromCluster -f $Number) $diskInstance = Get-CimInstance -ClassName MSCluster_Disk -Namespace 'Root\MSCluster' -Filter "Number = $Number" $diskResource = Get-ClusterResource | - Where-Object -FilterScript { $_.ResourceType -eq 'Physical Disk' } | - Where-Object -FilterScript { ($_ | Get-ClusterParameter -Name DiskIdGuid).Value -eq $diskInstance.Id } + Where-Object -FilterScript { $_.ResourceType -eq 'Physical Disk' } | + Where-Object -FilterScript { + ($_ | Get-ClusterParameter -Name DiskIdGuid).Value -eq $diskInstance.Id + } # Remove the cluster disk $diskResource | Remove-ClusterResource -Force @@ -152,6 +165,8 @@ function Test-TargetResource $Label ) + Write-Verbose -Message ($script:localizedData.EvaluatingClusterDiskInformation -f $Number) + $getTargetResourceResult = Get-TargetResource -Number $Number if($Ensure -eq 'Present') diff --git a/DSCResources/MSFT_xClusterDisk/en-US/MSFT_xClusterDisk.strings.psd1 b/DSCResources/MSFT_xClusterDisk/en-US/MSFT_xClusterDisk.strings.psd1 new file mode 100644 index 0000000..761c712 --- /dev/null +++ b/DSCResources/MSFT_xClusterDisk/en-US/MSFT_xClusterDisk.strings.psd1 @@ -0,0 +1,9 @@ +# Localized resources for xClusterDisk + +ConvertFrom-StringData @' + AddDiskToCluster = Add the disk {0} to the cluster. + SetDiskLabel = Set the disk label for the disk {0} to '{1}'. + RemoveDiskFromCluster = Remove the disk {0} from the cluster. + GetClusterDiskInformation = Retrieving information for cluster disk {0}. + EvaluatingClusterDiskInformation = Evaluating state of cluster disk {0}. +'@ diff --git a/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.psm1 b/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.psm1 index fd43531..494be5a 100644 --- a/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.psm1 +++ b/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.psm1 @@ -1,12 +1,17 @@ +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonResourceHelper.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xClusterNetwork' + <# .SYNOPSIS Returns the current state of the failover cluster network resource. .PARAMETER Address - The adress for the cluster network in the format '10.0.0.0'. + The address for the cluster network in the format '10.0.0.0'. .PARAMETER AddressMask - The adress mask for the cluster network in the format '255.255.255.0'. + The address mask for the cluster network in the format '255.255.255.0'. #> function Get-TargetResource { @@ -23,6 +28,8 @@ function Get-TargetResource $AddressMask ) + Write-Verbose -Message ($script:localizedData.GetClusterNetworkInformation -f $Name) + $NetworkResource = Get-ClusterNetwork | Where-Object -FilterScript { $_.Address -eq $Address -and $_.AddressMask -eq $AddressMask } @@ -42,10 +49,10 @@ function Get-TargetResource network resource. .PARAMETER Address - The adress for the cluster network in the format '10.0.0.0'. + The address for the cluster network in the format '10.0.0.0'. .PARAMETER AddressMask - The adress mask for the cluster network in the format '255.255.255.0'. + The address mask for the cluster network in the format '255.255.255.0'. .PARAMETER Name The name of the cluster network. If the cluster network name is not in @@ -100,7 +107,7 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('Name') -and $getTargetResourceResult.Name -ne $Name) { - Write-Verbose "Changing the name of network $Address/$AddressMask to '$Name'" + Write-Verbose -Message ($script:localizedData.ChangeNetworkName -f $Address, $AddressMask, $Name) $clusterNetworkResource = Get-ClusterNetwork | Where-Object -FilterScript { $_.Address -eq $Address -and $_.AddressMask -eq $AddressMask @@ -111,7 +118,7 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('Role') -and $getTargetResourceResult.Role -ne $Role) { - Write-Verbose "Changing the role of network $Address/$AddressMask to '$Role'" + Write-Verbose -Message ($script:localizedData.ChangeNetworkRole -f $Address, $AddressMask, $Role) $clusterNetworkResource = Get-ClusterNetwork | Where-Object -FilterScript { $_.Address -eq $Address -and $_.AddressMask -eq $AddressMask @@ -122,7 +129,7 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('Metric') -and $getTargetResourceResult.Metric -ne $Metric) { - Write-Verbose "Changing the metric of network $Address/$AddressMask to '$Metric'" + Write-Verbose -Message ($script:localizedData.ChangeNetworkMetric -f $Address, $AddressMask, $Metric) $clusterNetworkResource = Get-ClusterNetwork | Where-Object -FilterScript { $_.Address -eq $Address -and $_.AddressMask -eq $AddressMask @@ -138,10 +145,10 @@ function Set-TargetResource values for the properties Name, Role and Metric. .PARAMETER Address - The adress for the cluster network in the format '10.0.0.0'. + The address for the cluster network in the format '10.0.0.0'. .PARAMETER AddressMask - The adress mask for the cluster network in the format '255.255.255.0'. + The address mask for the cluster network in the format '255.255.255.0'. .PARAMETER Name The name of the cluster network. If the cluster network name is not in @@ -193,6 +200,8 @@ function Test-TargetResource $Metric ) + Write-Verbose -Message ($script:localizedData.EvaluatingClusterNetworkInformation -f $Name) + $getTargetResourceResult = Get-TargetResource -Address $Address -AddressMask $AddressMask $testTargetResourceReturnValue = $true diff --git a/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.schema.mof b/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.schema.mof index 3699b96..63945b7 100644 --- a/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.schema.mof +++ b/DSCResources/MSFT_xClusterNetwork/MSFT_xClusterNetwork.schema.mof @@ -1,8 +1,8 @@ [ClassVersion("1.0.0.0"), FriendlyName("xClusterNetwork")] class MSFT_xClusterNetwork : OMI_BaseResource { - [Key, Description("The adress for the cluster network in the format '10.0.0.0'.")] String Address; - [Key, Description("The adress mask for the cluster network in the format '255.255.255.0'.")] String AddressMask; + [Key, Description("The address for the cluster network in the format '10.0.0.0'.")] String Address; + [Key, Description("The address mask for the cluster network in the format '255.255.255.0'.")] String AddressMask; [Write, Description("The name of the cluster network. If the cluster network name is not in desired state it will be renamed to match this name.")] String Name; [Write, Description("he role of the cluster network. If the cluster network role is not in desired state it will change to match this role."), ValueMap{"0","1","3"}, Values{"0","1","3"}] String Role; [Write, Description("The metric number for the cluster network. If the cluster network metric number is not in desired state it will be changed to match this metric number.")] String Metric; diff --git a/DSCResources/MSFT_xClusterNetwork/en-US/MSFT_xClusterNetwork.strings.psd1 b/DSCResources/MSFT_xClusterNetwork/en-US/MSFT_xClusterNetwork.strings.psd1 new file mode 100644 index 0000000..06a0a88 --- /dev/null +++ b/DSCResources/MSFT_xClusterNetwork/en-US/MSFT_xClusterNetwork.strings.psd1 @@ -0,0 +1,9 @@ +# Localized resources for xClusterNetwork + +ConvertFrom-StringData @' + ChangeNetworkName = Changing the name of the network {0}/{1} to '{2}'. + ChangeNetworkRole = Changing the role of the network {0}/{1} to '{2}'. + ChangeNetworkMetric = Changing the metric of the network {0}/{1} to '{2}'. + GetClusterNetworkInformation = Retrieving information for cluster network {0}. + EvaluatingClusterNetworkInformation = Evaluating state of cluster network {0}. +'@ diff --git a/DSCResources/MSFT_xClusterPreferredOwner/MSFT_xClusterPreferredOwner.psm1 b/DSCResources/MSFT_xClusterPreferredOwner/MSFT_xClusterPreferredOwner.psm1 index 4dd9f2a..18a6813 100644 --- a/DSCResources/MSFT_xClusterPreferredOwner/MSFT_xClusterPreferredOwner.psm1 +++ b/DSCResources/MSFT_xClusterPreferredOwner/MSFT_xClusterPreferredOwner.psm1 @@ -1,3 +1,8 @@ +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonResourceHelper.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xClusterPreferredOwner' + <# .SYNOPSIS Returns the current state of the failover cluster group and cluster @@ -46,23 +51,22 @@ function Get-TargetResource $Ensure = 'Present' ) - Write-Verbose -Message "Retrieving Owner information for cluster $ClusterName..." + Write-Verbose -Message ($script:localizedData.GetOwnerInformationForCluster -f $ClusterName) $ownerNodes = @( - - Write-Verbose -Message "Retrieving Owner information for Cluster Group $ClusterGroup" - (((Get-ClusterGroup -Cluster $ClusterName) | Where-Object -FilterScript { - $_.Name -like $ClusterGroup - } | Get-ClusterOwnerNode).OwnerNodes).Name + Write-Verbose -Message ($script:localizedData.GetOwnerInformationForClusterGroup -f $ClusterGroup) + ((Get-ClusterGroup -Cluster $ClusterName | Where-Object -FilterScript { + $_.Name -like $ClusterGroup + } | Get-ClusterOwnerNode).OwnerNodes).Name if ($ClusterResources) { foreach ($resource in $ClusterResources) { - Write-Verbose -Message "Retrieving Owner information for Cluster Resource $resource" - (((Get-ClusterResource -Cluster $ClusterName) | Where-Object -FilterScript { - $_.Name -like $resource - } | Get-ClusterOwnerNode).OwnerNodes).Name + Write-Verbose -Message ($script:localizedData.GetOwnerInformationForClusterResource -f $resource) + ((Get-ClusterResource -Cluster $ClusterName | Where-Object -FilterScript { + $_.Name -like $resource + } | Get-ClusterOwnerNode).OwnerNodes).Name } } ) @@ -70,11 +74,11 @@ function Get-TargetResource $ownerNodes = $ownerNodes | Select-Object -Unique @{ - ClusterGroup = $ClusterGroup - Clustername = $ClusterName - Nodes = $ownerNodes + ClusterGroup = $ClusterGroup + ClusterName = $ClusterName + Nodes = $ownerNodes ClusterResources = $ClusterResources - Ensure = $Ensure + Ensure = $Ensure } } @@ -125,29 +129,29 @@ function Set-TargetResource $Ensure = 'Present' ) - Write-Verbose -Message "Retrieving all owners from cluster $ClusterName" + Write-Verbose -Message ($script:localizedData.GetAllNodesOfCluster -f $ClusterName) $allNodes = (Get-ClusterNode -Cluster $ClusterName).Name if ($Ensure -eq 'Present') { - Write-Verbose -Message "Setting Cluster owners for Group $ClusterGroup to $Nodes" - $null = (Get-ClusterGroup -Cluster $ClusterName) | Where-Object -FilterScript { + Write-Verbose -Message ($script:localizedData.SetOwnerForClusterGroup -f $ClusterGroup, $Nodes) + $null = Get-ClusterGroup -Cluster $ClusterName | Where-Object -FilterScript { $_.Name -like $ClusterGroup } | Set-ClusterOwnerNode -Owners $Nodes - $null = (Get-ClusterResource) | Where-Object { + $null = Get-ClusterResource | Where-Object { $_.OwnerGroup -like $ClusterGroup } | Set-ClusterOwnerNode -Owners $allNodes - Write-Verbose -Message "Moving Cluster Group $ClusterGroup to node $($Nodes[0])" - $null = (Get-ClusterGroup -Cluster $ClusterName) | Where-Object -FilterScript { + Write-Verbose -Message ($script:localizedData.MoveClusterGroup -f $ClusterGroup, $Nodes[0]) + $null = Get-ClusterGroup -Cluster $ClusterName | Where-Object -FilterScript { $_.name -like $ClusterGroup } | Move-ClusterGroup -Node $Nodes[0] foreach ($resource in $ClusterResources) { - Write-Verbose -Message "Setting Cluster owners for Resource $resource to $Nodes" - $null = (Get-ClusterResource -Cluster $ClusterName) | Where-Object -FilterScript { + Write-Verbose -Message ($script:localizedData.SetOwnerForClusterResource -f $resource, $Nodes) + $null = Get-ClusterResource -Cluster $ClusterName | Where-Object -FilterScript { $_.Name -like $resource } | Set-ClusterOwnerNode -Owners $Nodes } @@ -155,10 +159,10 @@ function Set-TargetResource if ($Ensure -eq 'Absent') { - Write-Verbose -Message "Retrieving current cluster owners for group $ClusterGroup" - $currentOwners = (((Get-ClusterGroup -Cluster $ClusterName) | Where-Object -FilterScript { - $_.Name -like $ClusterGroup - } | Get-ClusterOwnerNode).OwnerNodes).Name | Sort-Object -Unique + Write-Verbose -Message ($script:localizedData.GetOwnerInformationForClusterGroup -f $ClusterGroup) + $currentOwners = ((Get-ClusterGroup -Cluster $ClusterName | Where-Object -FilterScript { + $_.Name -like $ClusterGroup + } | Get-ClusterOwnerNode).OwnerNodes).Name | Sort-Object -Unique $newOwners = @( foreach ($currentOwner in $currentOwners) @@ -170,27 +174,27 @@ function Set-TargetResource } ) - Write-Verbose -Message "Removing owners from group $($ClusterGroup): $Nodes" - $null = (Get-ClusterGroup -Cluster $ClusterName) | Where-Object -FilterScript { + Write-Verbose -Message ($script:localizedData.RemoveOwnerFromClusterGroup -f $ClusterGroup, $Nodes) + $null = Get-ClusterGroup -Cluster $ClusterName | Where-Object -FilterScript { $_.Name -like $ClusterGroup } | Set-ClusterOwnerNode $newOwners - Write-Verbose -Message "Setting Cluster owners for Group $ClusterGroup to $newOwners" - $null = (Get-ClusterResource) | Where-Object -FilterScript { + Write-Verbose -Message ($script:localizedData.SetOwnerForClusterGroup -f $ClusterGroup, $newOwners) + $null = Get-ClusterResource | Where-Object -FilterScript { $_.OwnerGroup -like $ClusterGroup } | Set-ClusterOwnerNode $allNodes - Write-Verbose -Message "Moving Cluster Group $ClusterGroup to node $($newOwners[0])" - $null = (Get-ClusterGroup -Cluster $ClusterName) | Where-Object -FilterScript { + Write-Verbose -Message ($script:localizedData.MoveClusterGroup -f $ClusterGroup, $newOwners[0]) + $null = Get-ClusterGroup -Cluster $ClusterName | Where-Object -FilterScript { $_.Name -like $ClusterGroup } | Move-ClusterGroup -Node $newOwners[0] foreach ($resource in $ClusterResources) { - Write-Verbose -Message "Retrieving current cluster owners for resource $resource" + Write-Verbose -Message ($script:localizedData.GetOwnerInformationForClusterResource -f $resource) $currentOwners = ((Get-ClusterResource -Cluster $ClusterName | Where-Object -FilterScript { - $_.Name -like $resource - } | Get-ClusterOwnerNode).OwnerNodes).Name | Sort-Object -Unique + $_.Name -like $resource + } | Get-ClusterOwnerNode).OwnerNodes).Name | Sort-Object -Unique $newOwners = @( foreach ($currentOwner in $currentOwners) @@ -202,7 +206,7 @@ function Set-TargetResource } ) - Write-Verbose -Message "Setting Cluster owners for Resource $resource to $newOwners" + Write-Verbose -Message ($script:localizedData.SetOwnerForClusterResource -f $resource, $newOwners) $null = Get-ClusterResource -Cluster $ClusterName | Where-Object -FilterScript { $_.Name -like $resource } | Set-ClusterOwnerNode -Owners $newOwners @@ -259,7 +263,7 @@ function Test-TargetResource $Ensure = 'Present' ) - Write-Verbose -Message "Testing Owner information for cluster $ClusterName..." + Write-Verbose -Message ($script:localizedData.TestOwnerInformationForCluster -f $ClusterName) $getTargetResourceResult = (Get-TargetResource @PSBoundParameters).Nodes $result = $true @@ -270,7 +274,7 @@ function Test-TargetResource { if ($Nodes -notcontains $object) { - Write-Verbose -Message "$object was NOT found as possible owner" + Write-Verbose -Message ($script:localizedData.WasNotFoundAsPossibleOwner -f $object) $result = $false } } @@ -279,7 +283,7 @@ function Test-TargetResource { if ($getTargetResourceResult -notcontains $object) { - Write-Verbose -Message "$object was NOT found as possible owner" + Write-Verbose -Message ($script:localizedData.WasNotFoundAsPossibleOwner -f $object) $result = $false } } @@ -291,7 +295,7 @@ function Test-TargetResource { if ($Nodes -contains $object) { - Write-Verbose -Message "$object WAS found as possible owner" + Write-Verbose -Message ($script:localizedData.WasFoundAsPossibleOwner -f $object) $result = $false } } @@ -300,7 +304,7 @@ function Test-TargetResource { if ($getTargetResourceResult -contains $object) { - Write-Verbose -Message "$object WAS found as possible owner" + Write-Verbose -Message ($script:localizedData.WasFoundAsPossibleOwner -f $object) $result = $false } } diff --git a/DSCResources/MSFT_xClusterPreferredOwner/en-US/MSFT_xClusterPreferredOwner.strings.psd1 b/DSCResources/MSFT_xClusterPreferredOwner/en-US/MSFT_xClusterPreferredOwner.strings.psd1 new file mode 100644 index 0000000..c481da3 --- /dev/null +++ b/DSCResources/MSFT_xClusterPreferredOwner/en-US/MSFT_xClusterPreferredOwner.strings.psd1 @@ -0,0 +1,15 @@ +# Localized resources for xClusterPreferredOwner + +ConvertFrom-StringData @' + GetOwnerInformationForCluster = Retrieving owner information for cluster {0}. + GetOwnerInformationForClusterGroup = Retrieving owner information for cluster role/group {0}. + GetOwnerInformationForClusterResource = Retrieving owner information for cluster resource {0}. + GetAllNodesOfCluster = Retrieving all nodes from cluster {0}. + SetOwnerForClusterGroup = Setting owners for cluster role/group {0} to {1}. + SetOwnerForClusterResource = Setting owners for cluster resource {0} to {1}. + MoveClusterGroup = Moving cluster role/group {0} to node {1}. + RemoveOwnerFromClusterGroup = Removing owners {1} from cluster role/group {0}. + TestOwnerInformationForCluster = Testing Owner information for cluster {0}. + WasFoundAsPossibleOwner = {0} was found as possible owner. + WasNotFoundAsPossibleOwner = {0} was NOT found as possible owner. +'@ diff --git a/DSCResources/MSFT_xClusterQuorum/MSFT_xClusterQuorum.psm1 b/DSCResources/MSFT_xClusterQuorum/MSFT_xClusterQuorum.psm1 index 783b25c..4967de0 100644 --- a/DSCResources/MSFT_xClusterQuorum/MSFT_xClusterQuorum.psm1 +++ b/DSCResources/MSFT_xClusterQuorum/MSFT_xClusterQuorum.psm1 @@ -1,84 +1,116 @@ +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` +-ChildPath 'CommonResourceHelper.psm1') +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xClusterQuorum' + +<# + .SYNOPSIS + Returns the current state of the failover cluster quorum. + + .PARAMETER IsSingleInstance + Specifies the resource is a single instance, the value must be 'Yes'. +#> function Get-TargetResource { [CmdletBinding()] - [OutputType([Hashtable])] + [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] [ValidateSet('Yes')] - [String] $IsSingleInstance, - - [Parameter(Mandatory = $false)] - [ValidateSet('NodeMajority', 'NodeAndDiskMajority', 'NodeAndFileShareMajority', 'DiskOnly')] - [String] $Type, - - [Parameter(Mandatory = $false)] - [String] $Resource + [System.String] + $IsSingleInstance ) - $ClusterQuorum = Get-ClusterQuorum + Write-Verbose -Message $script:localizedData.GetClusterQuorumInformation + + $getClusterQuorumResult = Get-ClusterQuorum - switch ($ClusterQuorum.QuorumType) + switch ($getClusterQuorumResult.QuorumType) { # WS2016 only - 'Majority' { - if ($ClusterQuorum.QuorumResource -eq $null) + 'Majority' + { + if ($null -eq $getClusterQuorumResult.QuorumResource) { - $ClusterQuorumType = 'NodeMajority' + $clusterQuorumType = 'NodeMajority' } - elseif ($ClusterQuorum.QuorumResource.ResourceType.DisplayName -eq 'Physical Disk') + elseif ($getClusterQuorumResult.QuorumResource.ResourceType.DisplayName -eq 'Physical Disk') { - $ClusterQuorumType = 'NodeAndDiskMajority' + $clusterQuorumType = 'NodeAndDiskMajority' } - elseif ($ClusterQuorum.QuorumResource.ResourceType.DisplayName -eq 'File Share Witness') + elseif ($getClusterQuorumResult.QuorumResource.ResourceType.DisplayName -eq 'File Share Witness') { - $ClusterQuorumType = 'NodeAndFileShareMajority' + $clusterQuorumType = 'NodeAndFileShareMajority' } else { - throw "Unknown quorum resource: $($ClusterQuorum.QuorumResource)" + throw "Unknown quorum resource: $($getClusterQuorumResult.QuorumResource)" } } # WS2012R2 only - 'NodeMajority' { - $ClusterQuorumType = 'NodeMajority' + 'NodeMajority' + { + $clusterQuorumType = 'NodeMajority' } - 'NodeAndDiskMajority' { - $ClusterQuorumType = 'NodeAndDiskMajority' + + 'NodeAndDiskMajority' + { + $clusterQuorumType = 'NodeAndDiskMajority' } - 'NodeAndFileShareMajority' { - $ClusterQuorumType = 'NodeAndFileShareMajority' + + 'NodeAndFileShareMajority' + { + $clusterQuorumType = 'NodeAndFileShareMajority' } # All - 'DiskOnly' { - $ClusterQuorumType = 'DiskOnly' + 'DiskOnly' + { + $clusterQuorumType = 'DiskOnly' } # Default - default { - throw "Unknown quorum type: $($ClusterQuorum.QuorumType)" + default + { + throw "Unknown quorum type: $($getClusterQuorumResult.QuorumType)" } } - if ($ClusterQuorumType -eq 'NodeAndFileShareMajority') + if ($clusterQuorumType -eq 'NodeAndFileShareMajority') { - $ClusterQuorumResource = $ClusterQuorum.QuorumResource | Get-ClusterParameter -Name SharePath | Select-Object -ExpandProperty Value + $clusterQuorumResource = $getClusterQuorumResult.QuorumResource | + Get-ClusterParameter -Name SharePath | + Select-Object -ExpandProperty Value } else { - $ClusterQuorumResource = [String] $ClusterQuorum.QuorumResource.Name + $clusterQuorumResource = [String] $getClusterQuorumResult.QuorumResource.Name } @{ IsSingleInstance = $IsSingleInstance - Type = $ClusterQuorumType - Resource = $ClusterQuorumResource + Type = $clusterQuorumType + Resource = $clusterQuorumResource } } +<# + .SYNOPSIS + Configures the failover cluster quorum. + + .PARAMETER IsSingleInstance + Specifies the resource is a single instance, the value must be 'Yes'. + + .PARAMETER Type + Quorum type to use. Can be set to either NodeMajority, NodeAndDiskMajority, + NodeAndFileShareMajority or DiskOnly. + + .PARAMETER Resource + The name of the disk or file share resource to use as witness. This parameter + is optional if the quorum type is set to NodeMajority. +#> function Set-TargetResource { [CmdletBinding()] @@ -86,36 +118,60 @@ function Set-TargetResource ( [Parameter(Mandatory = $true)] [ValidateSet('Yes')] - [String] $IsSingleInstance, + [System.String] + $IsSingleInstance, - [Parameter(Mandatory = $false)] + [Parameter()] [ValidateSet('NodeMajority', 'NodeAndDiskMajority', 'NodeAndFileShareMajority', 'DiskOnly')] - [String] $Type, - - [Parameter(Mandatory = $false)] - [String] $Resource + [System.String] + $Type, + + [Parameter()] + [System.String] + $Resource ) + Write-Verbose -Message ($script:localizedData.SetClusterQuorum -f $Type) + switch ($Type) { - 'NodeMajority' { + 'NodeMajority' + { Set-ClusterQuorum -NoWitness } - 'NodeAndDiskMajority' { + 'NodeAndDiskMajority' + { Set-ClusterQuorum -DiskWitness $Resource } - 'NodeAndFileShareMajority' { + 'NodeAndFileShareMajority' + { Set-ClusterQuorum -FileShareWitness $Resource } - 'DiskOnly' { + 'DiskOnly' + { Set-ClusterQuorum -DiskOnly $Resource } } } +<# + .SYNOPSIS + Tests the current state of the failover cluster quorum. + + .PARAMETER IsSingleInstance + Specifies the resource is a single instance, the value must be 'Yes'. + + .PARAMETER Type + Quorum type to use. Can be set to either NodeMajority, NodeAndDiskMajority, + NodeAndFileShareMajority or DiskOnly. + + .PARAMETER Resource + The name of the disk or file share resource to use as witness. This parameter + is optional if the quorum type is set to NodeMajority. +#> function Test-TargetResource { [CmdletBinding()] @@ -124,22 +180,31 @@ function Test-TargetResource ( [Parameter(Mandatory = $true)] [ValidateSet('Yes')] - [String] $IsSingleInstance, + [System.String] + $IsSingleInstance, - [Parameter(Mandatory = $false)] + [Parameter()] [ValidateSet('NodeMajority', 'NodeAndDiskMajority', 'NodeAndFileShareMajority', 'DiskOnly')] - [String] $Type, - - [Parameter(Mandatory = $false)] - [String] $Resource - ) - - $CurrentQuorum = Get-TargetResource -IsSingleInstance $IsSingleInstance - - return ( - ($CurrentQuorum.Type -eq $Type) -and - ($CurrentQuorum.Resource -eq $Resource) + [System.String] + $Type, + + [Parameter()] + [System.String] + $Resource ) + + Write-Verbose -Message $script:localizedData.EvaluatingClusterQuorumInformation + + $getGetTargetResourceResult = Get-TargetResource -IsSingleInstance $IsSingleInstance + + $testTargetResourceReturnValue = $false + + if ($getGetTargetResourceResult.Type -eq $Type -and $getGetTargetResourceResult.Resource -eq $Resource) + { + $testTargetResourceReturnValue = $true + } + + $testTargetResourceReturnValue } Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xClusterQuorum/MSFT_xClusterQuorum.schema.mof b/DSCResources/MSFT_xClusterQuorum/MSFT_xClusterQuorum.schema.mof index ef0851d..0e22d07 100644 --- a/DSCResources/MSFT_xClusterQuorum/MSFT_xClusterQuorum.schema.mof +++ b/DSCResources/MSFT_xClusterQuorum/MSFT_xClusterQuorum.schema.mof @@ -1,9 +1,7 @@ [ClassVersion("1.0.0.0"), FriendlyName("xClusterQuorum")] class MSFT_xClusterQuorum : OMI_BaseResource { - [Key, ValueMap{"Yes"}, Values{"Yes"}] string IsSingleInstance; - - [Write, ValueMap{"NodeMajority", "NodeAndDiskMajority", "NodeAndFileShareMajority", "DiskOnly"}, Values{"NodeMajority", "NodeAndDiskMajority", "NodeAndFileShareMajority", "DiskOnly"}] string Type; - - [Write] String Resource; + [Key, Description("Specifies the resource is a single instance, the value must be 'Yes'."), ValueMap{"Yes"}, Values{"Yes"}] string IsSingleInstance; + [Write, Description("Quorum type to use. Can be set to either NodeMajority, NodeAndDiskMajority, NodeAndFileShareMajority or DiskOnly."), ValueMap{"NodeMajority", "NodeAndDiskMajority", "NodeAndFileShareMajority", "DiskOnly"}, Values{"NodeMajority", "NodeAndDiskMajority", "NodeAndFileShareMajority", "DiskOnly"}] string Type; + [Write, Description("The name of the disk or file share resource to use as witness. This parameter is optional if the quorum type is set to NodeMajority.")] String Resource; }; diff --git a/DSCResources/MSFT_xClusterQuorum/en-US/MSFT_xClusterQuorum.strings.psd1 b/DSCResources/MSFT_xClusterQuorum/en-US/MSFT_xClusterQuorum.strings.psd1 new file mode 100644 index 0000000..26d01cd --- /dev/null +++ b/DSCResources/MSFT_xClusterQuorum/en-US/MSFT_xClusterQuorum.strings.psd1 @@ -0,0 +1,7 @@ +# Localized resources for xClusterQuorum + +ConvertFrom-StringData @' + GetClusterQuorumInformation = Retrieving quorum information for cluster. + SetClusterQuorum = Setting quorum to type '{0}'. + EvaluatingClusterQuorumInformation = Evaluating state of quorum for cluster. +'@ diff --git a/DSCResources/MSFT_xWaitForCluster/MSFT_xWaitForCluster.psm1 b/DSCResources/MSFT_xWaitForCluster/MSFT_xWaitForCluster.psm1 index e222ea2..b833631 100644 --- a/DSCResources/MSFT_xWaitForCluster/MSFT_xWaitForCluster.psm1 +++ b/DSCResources/MSFT_xWaitForCluster/MSFT_xWaitForCluster.psm1 @@ -1,124 +1,183 @@ -# -# xWaitForCluster: DSC Resource that will wait for given name of Cluster, it checks the state of the cluster for given # interval until the cluster is found or the number of retries is reached. -# -# +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` +-ChildPath 'CommonResourceHelper.psm1') +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xWaitForCluster' -# -# The Get-TargetResource cmdlet. -# +<# + .SYNOPSIS + Get the values for which failover cluster and for how long to wait for the + cluster to exist. + + .PARAMETER Name + Name of the cluster to wait for. + + .PARAMETER RetryIntervalSec + Interval to check for cluster existence. Default values is 10 seconds. + + .PARAMETER RetryCount + Maximum number of retries to check for cluster existence. Default value + is 50 retries. +#> function Get-TargetResource { - [OutputType([Hashtable])] + [OutputType([System.Collections.Hashtable])] param - ( - [parameter(Mandatory)][string] $Name, - - [UInt64] $RetryIntervalSec = 10, - [UInt32] $RetryCount = 50 + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.UInt64] + $RetryIntervalSec = 10, + + [Parameter()] + [System.UInt32] + $RetryCount = 50 ) + Write-Verbose -Message $script:localizedData.ReturnParameterValues + @{ - Name = $Name + Name = $Name RetryIntervalSec = $RetryIntervalSec - RetryCount = $RetryCount + RetryCount = $RetryCount } } -# -# The Set-TargetResource cmdlet. -# +<# + .SYNOPSIS + Waits for the specific failover cluster to exist. It will throw an error if the + cluster has not been detected during the timeout period. + + .PARAMETER Name + Name of the cluster to wait for. + + .PARAMETER RetryIntervalSec + Interval to check for cluster existence. Default values is 10 seconds. + + .PARAMETER RetryCount + Maximum number of retries to check for cluster existence. Default value + is 50 retries. +#> function Set-TargetResource { param - ( - [parameter(Mandatory)][string] $Name, - - [UInt64] $RetryIntervalSec = 10, - [UInt32] $RetryCount = 50 + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.UInt64] + $RetryIntervalSec = 10, + + [Parameter()] + [System.UInt32] + $RetryCount = 50 ) $clusterFound = $false - Write-Verbose -Message "Checking for cluster $Name ..." + Write-Verbose -Message ($script:localizedData.CheckClusterPresent -f $Name) for ($count = 0; $count -lt $RetryCount; $count++) { try { - $ComputerInfo = Get-WmiObject Win32_ComputerSystem - if (($ComputerInfo -eq $null) -or ($ComputerInfo.Domain -eq $null)) + $computerObject = Get-CimInstance -ClassName Win32_ComputerSystem + if ($null -eq $computerObject -or $null -eq $computerObject.Domain) { - Write-Verbose -Message "Can't find machine's domain name" - break; + Write-Verbose -Message $script:localizedData.TargetNodeDomainMissing + break } - $cluster = Get-Cluster -Name $Name -Domain $ComputerInfo.Domain + $cluster = Get-Cluster -Name $Name -Domain $computerObject.Domain - if ($cluster -ne $null) + if ($null -ne $cluster) { - Write-Verbose -Message "Found cluster $Name" + Write-Verbose -Message ($script:localizedData.ClusterPresent -f $Name) $clusterFound = $true - - break; + break } - } catch { - Write-Verbose -Message "Cluster $Name not found. Will retry again after $RetryIntervalSec sec" + Write-Verbose -Message ($script:localizedData.ClusterAbsent -f $Name, $RetryIntervalSec) } - - Write-Verbose -Message "Cluster $Name not found. Will retry again after $RetryIntervalSec sec" + + Write-Verbose -Message ($script:localizedData.ClusterAbsent -f $Name, $RetryIntervalSec) Start-Sleep -Seconds $RetryIntervalSec } - if (! $clusterFound) + if (-not $clusterFound) { - throw "Cluster $Name not found after $count attempts with $RetryIntervalSec sec interval" + $errorMessage = $script:localizedData.ClusterAbsentAfterTimeOut -f $Name, $count, $RetryIntervalSec + New-InvalidOperationException -Message $errorMessage } } -# -# The Test-TargetResource cmdlet. -# +<# + .SYNOPSIS + Test if the specific failover cluster exist. + + .PARAMETER Name + Name of the cluster to wait for. + + .PARAMETER RetryIntervalSec + Interval to check for cluster existence. Default values is 10 seconds. + + .PARAMETER RetryCount + Maximum number of retries to check for cluster existence. Default value + is 50 retries. +#> function Test-TargetResource { [OutputType([Boolean])] param - ( - [parameter(Mandatory)][string] $Name, - - [UInt64] $RetryIntervalSec = 10, - [UInt32] $RetryCount = 50 + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.UInt64] + $RetryIntervalSec = 10, + + [Parameter()] + [System.UInt32] + $RetryCount = 50 ) - Write-Verbose -Message "Checking for Cluster $Name ..." + Write-Verbose -Message ($script:localizedData.EvaluatingClusterPresent -f $Name) + + $testTargetResourceReturnValue = $false try { - $ComputerInfo = Get-WmiObject Win32_ComputerSystem - if (($ComputerInfo -eq $null) -or ($ComputerInfo.Domain -eq $null)) - { - Write-Verbose -Message "Can't find machine's domain name" - $false - } - - $cluster = Get-Cluster -Name $Name -Domain $ComputerInfo.Domain - if ($cluster -eq $null) + $computerObject = Get-CimInstance -ClassName Win32_ComputerSystem + if ($null -eq $computerObject -or $null -eq $computerObject.Domain) { - Write-Verbose -Message "Cluster $Name not found in domain $ComputerInfo.Domain" - $false + Write-Verbose -Message $script:localizedData.TargetNodeDomainMissing } else { - Write-Verbose -Message "Found cluster $Name" - $true + $cluster = Get-Cluster -Name $Name -Domain $computerObject.Domain + if ($null -eq $cluster) + { + Write-Verbose -Message ($script:localizedData.ClusterAbsentWithDomain -f $Name, $computerObject.Domain) + } + else + { + Write-Verbose -Message ($script:localizedData.ClusterPresent -f $Name) + $testTargetResourceReturnValue = $true + } } } catch { - Write-Verbose -Message "Cluster $Name not found" - $false + Write-Verbose -Message ($script:localizedData.ClusterAbsentWithError -f $Name, $_.Message) } + + $testTargetResourceReturnValue } diff --git a/DSCResources/MSFT_xWaitForCluster/MSFT_xWaitForCluster.schema.mof b/DSCResources/MSFT_xWaitForCluster/MSFT_xWaitForCluster.schema.mof index a3f9d33..6da3968 100644 --- a/DSCResources/MSFT_xWaitForCluster/MSFT_xWaitForCluster.schema.mof +++ b/DSCResources/MSFT_xWaitForCluster/MSFT_xWaitForCluster.schema.mof @@ -1,16 +1,11 @@ #pragma namespace("\\\\.\\root\\microsoft\\windows\\DesiredStateConfiguration") -[ClassVersion("1.0.0"), FriendlyName("xWaitForCluster")] +[ClassVersion("1.0.0"), FriendlyName("xWaitForCluster")] class MSFT_xWaitForCluster : OMI_BaseResource { - [key, Description("Name of the cluster")] - string Name; - - [Write, Description("Interval to check the cluster existency")] - Uint64 RetryIntervalSec; - - [Write, Description("Maximum number of retries to check cluster existency")] - Uint32 RetryCount; + [Key, Description("Name of the cluster to wait for.")] string Name; + [Write, Description("Interval to check for cluster existence. Default values is 10 seconds.")] Uint64 RetryIntervalSec; + [Write, Description("Maximum number of retries to check for cluster existence. Default value is 50 retries.")] Uint32 RetryCount; }; diff --git a/DSCResources/MSFT_xWaitForCluster/en-US/MSFT_xWaitForCluster.strings.psd1 b/DSCResources/MSFT_xWaitForCluster/en-US/MSFT_xWaitForCluster.strings.psd1 new file mode 100644 index 0000000..60e652f --- /dev/null +++ b/DSCResources/MSFT_xWaitForCluster/en-US/MSFT_xWaitForCluster.strings.psd1 @@ -0,0 +1,13 @@ +# Localized resources for xWaitForCluster + +ConvertFrom-StringData @' + ReturnParameterValues = Returning the values passed as parameters. + CheckClusterPresent = Checking if cluster {0} is present. + TargetNodeDomainMissing = Can't find the target node's domain name. + ClusterPresent = Cluster {0} is present. + ClusterAbsent = Cluster {0} is NOT present. Will retry again after {1} seconds. + ClusterAbsentWithDomain = Cluster {0} is NOT present in Active Directory domain '{1}'. + ClusterAbsentWithError = Cluster {0} is NOT present with error: {1} + ClusterAbsentAfterTimeOut = Failover cluster {0} was not found after {1} attempts with {2} seconds interval. + EvaluatingClusterPresent = Evaluating if cluster {0} is present. +'@ diff --git a/Examples/Resources/xCluster/1-CreateFirstNodeOfAFailoverCluster.ps1 b/Examples/Resources/xCluster/1-CreateFirstNodeOfAFailoverCluster.ps1 index 046dbe7..0b3bfcc 100644 --- a/Examples/Resources/xCluster/1-CreateFirstNodeOfAFailoverCluster.ps1 +++ b/Examples/Resources/xCluster/1-CreateFirstNodeOfAFailoverCluster.ps1 @@ -18,27 +18,27 @@ Configuration Example WindowsFeature AddFailoverFeature { Ensure = 'Present' - Name = 'Failover-clustering' + Name = 'Failover-clustering' } WindowsFeature AddRemoteServerAdministrationToolsClusteringPowerShellFeature { - Ensure = 'Present' - Name = 'RSAT-Clustering-PowerShell' + Ensure = 'Present' + Name = 'RSAT-Clustering-PowerShell' DependsOn = '[WindowsFeature]AddFailoverFeature' } WindowsFeature AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature { - Ensure = 'Present' - Name = 'RSAT-Clustering-CmdInterface' + Ensure = 'Present' + Name = 'RSAT-Clustering-CmdInterface' DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringPowerShellFeature' } xCluster CreateCluster { - Name = 'Cluster01' - StaticIPAddress = '192.168.100.20/24' + Name = 'Cluster01' + StaticIPAddress = '192.168.100.20/24' <# This user must have the permission to create the CNO (Cluster Name Object) in Active Directory, @@ -46,7 +46,7 @@ Configuration Example #> DomainAdministratorCredential = $ActiveDirectoryAdministratorCredential - DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature' - } + DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature' + } } } diff --git a/Examples/Resources/xCluster/2-JoinAdditionalNodeToFailoverCluster.ps1 b/Examples/Resources/xCluster/2-JoinAdditionalNodeToFailoverCluster.ps1 index d628bd6..a9e7d40 100644 --- a/Examples/Resources/xCluster/2-JoinAdditionalNodeToFailoverCluster.ps1 +++ b/Examples/Resources/xCluster/2-JoinAdditionalNodeToFailoverCluster.ps1 @@ -18,37 +18,37 @@ Configuration Example WindowsFeature AddFailoverFeature { Ensure = 'Present' - Name = 'Failover-clustering' + Name = 'Failover-clustering' } WindowsFeature AddRemoteServerAdministrationToolsClusteringPowerShellFeature { - Ensure = 'Present' - Name = 'RSAT-Clustering-PowerShell' + Ensure = 'Present' + Name = 'RSAT-Clustering-PowerShell' DependsOn = '[WindowsFeature]AddFailoverFeature' } WindowsFeature AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature { - Ensure = 'Present' - Name = 'RSAT-Clustering-CmdInterface' + Ensure = 'Present' + Name = 'RSAT-Clustering-CmdInterface' DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringPowerShellFeature' } xWaitForCluster WaitForCluster { - Name = 'Cluster01' + Name = 'Cluster01' RetryIntervalSec = 10 - RetryCount = 60 - DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature' + RetryCount = 60 + DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature' } xCluster JoinSecondNodeToCluster { - Name = 'Cluster01' - StaticIPAddress = '192.168.100.20/24' + Name = 'Cluster01' + StaticIPAddress = '192.168.100.20/24' DomainAdministratorCredential = $ActiveDirectoryAdministratorCredential - DependsOn = '[xWaitForCluster]WaitForCluster' + DependsOn = '[xWaitForCluster]WaitForCluster' } } } diff --git a/Examples/Resources/xCluster/3-CreateFailoverClusterWithTwoNodes.ps1 b/Examples/Resources/xCluster/3-CreateFailoverClusterWithTwoNodes.ps1 index 82bd5dd..9b01e38 100644 --- a/Examples/Resources/xCluster/3-CreateFailoverClusterWithTwoNodes.ps1 +++ b/Examples/Resources/xCluster/3-CreateFailoverClusterWithTwoNodes.ps1 @@ -14,7 +14,7 @@ $ConfigurationData = @{ AllNodes = @( @{ - NodeName= '*' + NodeName = '*' <# Replace with the correct path to your own public certificate part of the same certificate @@ -41,7 +41,7 @@ $ConfigurationData = @{ parameter CertificateFile. For this example it is assumed that both machines have the same certificate installed. #> - Thumbprint = "E513EEFCB763E6954C52BA66A1A81231BF3F551E" + Thumbprint = "E513EEFCB763E6954C52BA66A1A81231BF3F551E" <# Replace with your own CNO (Cluster Name Object) and IP address. @@ -51,27 +51,27 @@ $ConfigurationData = @{ If the CNO is not prestaged, then the credential used in the xCluster resource must have the permission in Active Directory to create the CNO (Cluster Name Object). #> - ClusterName = 'Cluster01' - ClusterIPAddress = '192.168.100.20/24' + ClusterName = 'Cluster01' + ClusterIPAddress = '192.168.100.20/24' }, # Node01 - First cluster node. @{ # Replace with the name of the actual target node. - NodeName= 'Node01' + NodeName = 'Node01' # This is used in the configuration to know which resource to compile. - Role = 'FirstServerNode' - }, + Role = 'FirstServerNode' + }, - # Node02 - Second cluster node - @{ + # Node02 - Second cluster node + @{ # Replace with the name of the actual target node. - NodeName= 'Node02' + NodeName = 'Node02' # This is used in the configuration to know which resource to compile. - Role = 'AdditionalServerNode' - } + Role = 'AdditionalServerNode' + } ) } @@ -90,31 +90,31 @@ Configuration Example WindowsFeature AddFailoverFeature { Ensure = 'Present' - Name = 'Failover-clustering' + Name = 'Failover-clustering' } WindowsFeature AddRemoteServerAdministrationToolsClusteringPowerShellFeature { - Ensure = 'Present' - Name = 'RSAT-Clustering-PowerShell' + Ensure = 'Present' + Name = 'RSAT-Clustering-PowerShell' DependsOn = '[WindowsFeature]AddFailoverFeature' } WindowsFeature AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature { - Ensure = 'Present' - Name = 'RSAT-Clustering-CmdInterface' + Ensure = 'Present' + Name = 'RSAT-Clustering-CmdInterface' DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringPowerShellFeature' } xCluster CreateCluster { - Name = $Node.ClusterName - StaticIPAddress = $Node.ClusterIPAddress + Name = $Node.ClusterName + StaticIPAddress = $Node.ClusterIPAddress # This user must have the permission to create the CNO (Cluster Name Object) in Active Directory, unless it is prestaged. DomainAdministratorCredential = $ActiveDirectoryAdministratorCredential - DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature' - } + DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature' + } } Node $AllNodes.Where{ $_.Role -eq 'AdditionalServerNode' }.NodeName @@ -122,37 +122,37 @@ Configuration Example WindowsFeature AddFailoverFeature { Ensure = 'Present' - Name = 'Failover-clustering' + Name = 'Failover-clustering' } WindowsFeature AddRemoteServerAdministrationToolsClusteringPowerShellFeature { - Ensure = 'Present' - Name = 'RSAT-Clustering-PowerShell' + Ensure = 'Present' + Name = 'RSAT-Clustering-PowerShell' DependsOn = '[WindowsFeature]AddFailoverFeature' } WindowsFeature AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature { - Ensure = 'Present' - Name = 'RSAT-Clustering-CmdInterface' + Ensure = 'Present' + Name = 'RSAT-Clustering-CmdInterface' DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringPowerShellFeature' } xWaitForCluster WaitForCluster { - Name = $Node.ClusterName + Name = $Node.ClusterName RetryIntervalSec = 10 - RetryCount = 60 - DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature' + RetryCount = 60 + DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature' } xCluster JoinSecondNodeToCluster { - Name = $Node.ClusterName - StaticIPAddress = $Node.ClusterIPAddress + Name = $Node.ClusterName + StaticIPAddress = $Node.ClusterIPAddress DomainAdministratorCredential = $ActiveDirectoryAdministratorCredential - DependsOn = '[xWaitForCluster]WaitForCluster' + DependsOn = '[xWaitForCluster]WaitForCluster' } } } diff --git a/Examples/Resources/xClusterDisk/1-AddClusterDisk.ps1 b/Examples/Resources/xClusterDisk/1-AddClusterDisk.ps1 index 8677b3d..ca50536 100644 --- a/Examples/Resources/xClusterDisk/1-AddClusterDisk.ps1 +++ b/Examples/Resources/xClusterDisk/1-AddClusterDisk.ps1 @@ -16,14 +16,14 @@ Configuration Example { Number = 1 Ensure = 'Present' - Label = 'SQL2016-DATA' + Label = 'SQL2016-DATA' } xClusterDisk 'AddClusterDisk-SQL2017-LOG' { Number = 2 Ensure = 'Present' - Label = 'SQL2016-LOG' + Label = 'SQL2016-LOG' } } } diff --git a/Examples/Resources/xClusterDisk/2-RemoveClusterDisk.ps1 b/Examples/Resources/xClusterDisk/2-RemoveClusterDisk.ps1 index 867c4a7..412b7a8 100644 --- a/Examples/Resources/xClusterDisk/2-RemoveClusterDisk.ps1 +++ b/Examples/Resources/xClusterDisk/2-RemoveClusterDisk.ps1 @@ -16,14 +16,14 @@ Configuration Example { Number = 1 Ensure = 'Absent' - Label = 'SQL2016-DATA' + Label = 'SQL2016-DATA' } xClusterDisk 'AddClusterDisk-SQL2017-LOG' { Number = 2 Ensure = 'Absent' - Label = 'SQL2016-LOG' + Label = 'SQL2016-LOG' } } } diff --git a/Examples/Resources/xClusterNetwork/1-ChangeClusterNetwork.ps1 b/Examples/Resources/xClusterNetwork/1-ChangeClusterNetwork.ps1 index d857d4c..ac8cd16 100644 --- a/Examples/Resources/xClusterNetwork/1-ChangeClusterNetwork.ps1 +++ b/Examples/Resources/xClusterNetwork/1-ChangeClusterNetwork.ps1 @@ -14,20 +14,20 @@ Configuration Example { xClusterNetwork 'ChangeNetwork-10' { - Address = '10.0.0.0' + Address = '10.0.0.0' AddressMask = '255.255.255.0' - Name = 'Client1' - Role = '3' - Metric = '10' + Name = 'Client1' + Role = '3' + Metric = '10' } xClusterNetwork 'ChangeNetwork-192' { - Address = '192.168.0.0' + Address = '192.168.0.0' AddressMask = '255.255.255.0' - Name = 'Heartbeat' - Role = '1' - Metric = '200' + Name = 'Heartbeat' + Role = '1' + Metric = '200' } } } diff --git a/Examples/Resources/xClusterPreferredOwner/1-AddPreferredOwner.ps1 b/Examples/Resources/xClusterPreferredOwner/1-AddPreferredOwner.ps1 index 794faa4..b8a6c49 100644 --- a/Examples/Resources/xClusterPreferredOwner/1-AddPreferredOwner.ps1 +++ b/Examples/Resources/xClusterPreferredOwner/1-AddPreferredOwner.ps1 @@ -14,20 +14,20 @@ Configuration Example { xClusterPreferredOwner 'AddOwnersForClusterGroup1' { - Ensure = 'Present' - ClusterName = 'TESTCLU1' - ClusterGroup = 'Cluster Group 1' - Nodes = @('Node1', 'Node2') - ClusterResources = @('Resource1','Resource2') + Ensure = 'Present' + ClusterName = 'TESTCLU1' + ClusterGroup = 'Cluster Group 1' + Nodes = @('Node1', 'Node2') + ClusterResources = @('Resource1', 'Resource2') } xClusterPreferredOwner 'AddOwnersForClusterGroup2' { - Ensure = 'Present' - ClusterName = 'TESTCLU1' - ClusterGroup = 'Cluster Group 2' - Nodes = @('Node1', 'Node2') - ClusterResources = @('Resource3','Resource4') + Ensure = 'Present' + ClusterName = 'TESTCLU1' + ClusterGroup = 'Cluster Group 2' + Nodes = @('Node1', 'Node2') + ClusterResources = @('Resource3', 'Resource4') } } } diff --git a/Examples/Resources/xClusterPreferredOwner/2-RemovePreferredOwner.ps1 b/Examples/Resources/xClusterPreferredOwner/2-RemovePreferredOwner.ps1 index a4675c4..0b7c78c 100644 --- a/Examples/Resources/xClusterPreferredOwner/2-RemovePreferredOwner.ps1 +++ b/Examples/Resources/xClusterPreferredOwner/2-RemovePreferredOwner.ps1 @@ -19,20 +19,20 @@ Configuration Example { xClusterPreferredOwner 'RemoveOwnersForClusterGroup1' { - Ensure = 'Absent' - ClusterName = 'TESTCLU1' - ClusterGroup = 'Cluster Group 1' - Nodes = @('Node1', 'Node2') - ClusterResources = @('Resource1','Resource2') + Ensure = 'Absent' + ClusterName = 'TESTCLU1' + ClusterGroup = 'Cluster Group 1' + Nodes = @('Node1', 'Node2') + ClusterResources = @('Resource1', 'Resource2') } xClusterPreferredOwner 'RemoveOwnersForClusterGroup2' { - Ensure = 'Absent' - ClusterName = 'TESTCLU1' - ClusterGroup = 'Cluster Group 2' - Nodes = @('Node1', 'Node2') - ClusterResources = @('Resource3','Resource4') + Ensure = 'Absent' + ClusterName = 'TESTCLU1' + ClusterGroup = 'Cluster Group 2' + Nodes = @('Node1', 'Node2') + ClusterResources = @('Resource3', 'Resource4') } } } diff --git a/Examples/Resources/xClusterQuorum/1-SetQuorumToNodeMajority.ps1 b/Examples/Resources/xClusterQuorum/1-SetQuorumToNodeMajority.ps1 new file mode 100644 index 0000000..30ac9a5 --- /dev/null +++ b/Examples/Resources/xClusterQuorum/1-SetQuorumToNodeMajority.ps1 @@ -0,0 +1,21 @@ +<# +.EXAMPLE + This example shows how to set the quorum in a failover cluster to use + node majority. + + This example assumes the failover cluster is already present. +#> + +Configuration Example +{ + Import-DscResource -ModuleName xFailOverCluster + + Node localhost + { + xClusterQuorum 'SetQuorumToNodeMajority' + { + IsSingleInstance = 'Yes' + Type = 'NodeMajority' + } + } +} diff --git a/Examples/Resources/xClusterQuorum/2-SetQuorumToNodeAndDiskMajority.ps1 b/Examples/Resources/xClusterQuorum/2-SetQuorumToNodeAndDiskMajority.ps1 new file mode 100644 index 0000000..db5a06e --- /dev/null +++ b/Examples/Resources/xClusterQuorum/2-SetQuorumToNodeAndDiskMajority.ps1 @@ -0,0 +1,22 @@ +<# +.EXAMPLE + This example shows how to set the quorum in a failover cluster to use + node and disk majority. + + This example assumes the failover cluster is already present. +#> + +Configuration Example +{ + Import-DscResource -ModuleName xFailOverCluster + + Node localhost + { + xClusterQuorum 'SetQuorumToNodeAndDiskMajority' + { + IsSingleInstance = 'Yes' + Type = 'NodeAndDiskMajority' + Resource = 'Witness Cluster Disk' + } + } +} diff --git a/Examples/Resources/xClusterQuorum/3-SetQuorumToNodeAndFileShareMajority.ps1 b/Examples/Resources/xClusterQuorum/3-SetQuorumToNodeAndFileShareMajority.ps1 new file mode 100644 index 0000000..2f762c8 --- /dev/null +++ b/Examples/Resources/xClusterQuorum/3-SetQuorumToNodeAndFileShareMajority.ps1 @@ -0,0 +1,30 @@ +<# +.EXAMPLE + This example shows how to set the quorum in a failover cluster to use + node and file share majority. + + This example assumes the failover cluster is already present. + + This example also assumes that path \\witness.company.local\witness$ is already + present and has the right permission to be used by the cluster. + Either the user running the configuration or the Cluster Name Object (CNO) + should have full control on the share to be able to create the witness folder + and set the permissions. More than one cluster can use the same share. + Here is a link for setting up the high availability for the file share witness + https://blogs.msdn.microsoft.com/clustering/2014/03/31/configuring-a-file-share-witness-on-a-scale-out-file-server/ +#> + +Configuration Example +{ + Import-DscResource -ModuleName xFailOverCluster + + Node localhost + { + xClusterQuorum 'SetQuorumToNodeAndDiskMajority' + { + IsSingleInstance = 'Yes' + Type = 'NodeAndFileShareMajority' + Resource = '\\witness.company.local\witness$' + } + } +} diff --git a/Examples/Resources/xClusterQuorum/4-SetQuorumToDiskOnly.ps1 b/Examples/Resources/xClusterQuorum/4-SetQuorumToDiskOnly.ps1 new file mode 100644 index 0000000..6f3e7f1 --- /dev/null +++ b/Examples/Resources/xClusterQuorum/4-SetQuorumToDiskOnly.ps1 @@ -0,0 +1,22 @@ +<# +.EXAMPLE + This example shows how to set the quorum in a failover cluster to use + disk only. + + This example assumes the failover cluster is already present. +#> + +Configuration Example +{ + Import-DscResource -ModuleName xFailOverCluster + + Node localhost + { + xClusterQuorum 'SetQuorumToDiskOnly' + { + IsSingleInstance = 'Yes' + Type = 'DiskOnly' + Resource = 'Witness Cluster Disk' + } + } +} diff --git a/Examples/Resources/xWaitForCluster/1-WaitForFailoverClusterToBePresent.ps1 b/Examples/Resources/xWaitForCluster/1-WaitForFailoverClusterToBePresent.ps1 index 117d181..a062057 100644 --- a/Examples/Resources/xWaitForCluster/1-WaitForFailoverClusterToBePresent.ps1 +++ b/Examples/Resources/xWaitForCluster/1-WaitForFailoverClusterToBePresent.ps1 @@ -21,37 +21,37 @@ Configuration Example WindowsFeature AddFailoverFeature { Ensure = 'Present' - Name = 'Failover-clustering' + Name = 'Failover-clustering' } WindowsFeature AddRemoteServerAdministrationToolsClusteringPowerShellFeature { - Ensure = 'Present' - Name = 'RSAT-Clustering-PowerShell' + Ensure = 'Present' + Name = 'RSAT-Clustering-PowerShell' DependsOn = '[WindowsFeature]AddFailoverFeature' } WindowsFeature AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature { - Ensure = 'Present' - Name = 'RSAT-Clustering-CmdInterface' + Ensure = 'Present' + Name = 'RSAT-Clustering-CmdInterface' DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringPowerShellFeature' } xWaitForCluster WaitForCluster { - Name = 'Cluster01' + Name = 'Cluster01' RetryIntervalSec = 10 - RetryCount = 60 - DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature' + RetryCount = 60 + DependsOn = '[WindowsFeature]AddRemoteServerAdministrationToolsClusteringCmdInterfaceFeature' } xCluster JoinSecondNodeToCluster { - Name = 'Cluster01' - StaticIPAddress = '192.168.100.20/24' + Name = 'Cluster01' + StaticIPAddress = '192.168.100.20/24' DomainAdministratorCredential = $ActiveDirectoryAdministratorCredential - DependsOn = '[xWaitForCluster]WaitForCluster' + DependsOn = '[xWaitForCluster]WaitForCluster' } } } diff --git a/LICENSE b/LICENSE index 567fd6a..a1d2d91 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Microsoft Corporation. +Copyright (c) 2017 Microsoft Corporation. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/README.md b/README.md index e6ca700..683a0ed 100644 --- a/README.md +++ b/README.md @@ -107,9 +107,9 @@ Configures a cluster network in a failover cluster. #### Parameters for xClusterNetwork -* **`[String]` Address** _(Key)_: The adress for the cluster network in the format +* **`[String]` Address** _(Key)_: The address for the cluster network in the format '10.0.0.0'. -* **`[String]` AddressMask** _(Key)_: The adress mask for the cluster network in +* **`[String]` AddressMask** _(Key)_: The address mask for the cluster network in the format '255.255.255.0'. * **`[String]` Name** _(Write)_: The name of the cluster network. If the cluster network name is not in desired state it will be renamed to match this name. @@ -132,7 +132,7 @@ The cluster network role can be set to either the value 0, 1 or 3. 3 = Allow cluster network communication and client connectivity See this article for more information about cluster network role values; -https://technet.microsoft.com/en-us/library/dn550728(v=ws.11).aspx +[Configuring Windows Failover Cluster Networks](https://blogs.technet.microsoft.com/askcore/2014/02/19/configuring-windows-failover-cluster-networks/) #### Examples for xClusterNetwork @@ -182,7 +182,10 @@ Configures quorum in a cluster. #### Examples for xClusterQuorum -None. +* [Set quorum to node majority](/Examples/Resources/xClusterQuorum/1-SetQuorumToNodeMajority.ps1) +* [Set quorum to node and disk majority](/Examples/Resources/xClusterQuorum/2-SetQuorumToNodeAndDiskMajority.ps1) +* [Set quorum to node and file share majority](/Examples/Resources/xClusterQuorum/3-SetQuorumToNodeAndFileShareMajority.ps1) +* [Set quorum to disk only](/Examples/Resources/xClusterQuorum/4-SetQuorumToDiskOnly.ps1) ### xWaitForCluster diff --git a/Tests/MSFT_xClusterQuorum.Tests.ps1 b/Tests/MSFT_xClusterQuorum.Tests.ps1 deleted file mode 100644 index 00ed8da..0000000 --- a/Tests/MSFT_xClusterQuorum.Tests.ps1 +++ /dev/null @@ -1,595 +0,0 @@ -[CmdletBinding()] -param -( -) - -if (!$PSScriptRoot) -{ - $PSScriptRoot = [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Path) -} - -$RootPath = (Resolve-Path -Path "$PSScriptRoot\..").Path -$ModuleName = 'MSFT_xClusterQuorum' - -try -{ - if (-not (Get-WindowsFeature -Name RSAT-Clustering-PowerShell -ErrorAction Stop).Installed) - { - Add-WindowsFeature -Name RSAT-Clustering-PowerShell -ErrorAction Stop - } -} -catch -{ - Write-Warning $_ -} - -Import-Module (Join-Path -Path $RootPath -ChildPath "DSCResources\$ModuleName\$ModuleName.psm1") -Force - - -## General test for the xClusterQuorum resource - -Describe 'xClusterQuorum' { - - InModuleScope $ModuleName { - - $TestParameter = @{ - IsSingleInstance = 'Yes' - Type = 'NodeAndDiskMajority' - Resource = 'Witness' - } - - Mock -CommandName 'Get-ClusterQuorum' -MockWith { - [PSCustomObject] @{ - Cluster = 'CLUSTER01' - QuorumResource = 'Witness' - QuorumType = 'NodeAndDiskMajority' - } - } - - Mock -CommandName 'Set-ClusterQuorum' -MockWith { - } - - Context 'Validate Get-TargetResource method' { - - It 'Returns a [System.Collection.Hashtable] type' { - - $Result = Get-TargetResource @TestParameter - - $Result -is [System.Collections.Hashtable] | Should Be $true - } - } - - Context 'Validate Set-TargetResource method' { - - It 'Returns nothing' { - - $Result = Set-TargetResource @TestParameter - - $Result -eq $null | Should Be $true - } - } - - Context 'Validate Test-TargetResource method' { - - It 'Returns a [System.Boolean] type' { - - $Result = Test-TargetResource @TestParameter - - $Result -is [System.Boolean] | Should Be $true - } - } - } -} - - -## Test NodeMajority quorum type - -Describe 'xClusterQuorum (NodeMajority / WS2012R2)' { - - InModuleScope $ModuleName { - - $TestParameter = @{ - IsSingleInstance = 'Yes' - Type = 'NodeMajority' - Resource = '' - } - - Mock -CommandName 'Get-ClusterQuorum' -MockWith { - [PSCustomObject] @{ - Cluster = 'CLUSTER01' - QuorumType = 'NodeMajority' - QuorumResource = $null - } - } - - Mock -CommandName 'Set-ClusterQuorum' -ParameterFilter { $NoWitness -eq $true } -MockWith { - } - - Context 'Validate Get-TargetResource method' { - - It 'Returns current configuration' { - - $Result = Get-TargetResource @TestParameter - - $Result.IsSingleInstance | Should Be $TestParameter.IsSingleInstance - $Result.Type | Should Be $TestParameter.Type - $Result.Resource | Should Be $TestParameter.Resource - } - } - - Context 'Validate Set-TargetResource method' { - - It 'Set the new configuration' { - - $Result = Set-TargetResource @TestParameter - - Assert-MockCalled -CommandName 'Set-ClusterQuorum' -ParameterFilter { $NoWitness -eq $true } -Times 1 - } - } - - Context 'Validate Test-TargetResource method' { - - It 'Check the current configuration' { - - $Result = Test-TargetResource @TestParameter - - $Result | Should Be $true - } - } - } -} - -Describe 'xClusterQuorum (NodeMajority / WS2016Prev)' { - - InModuleScope $ModuleName { - - $TestParameter = @{ - IsSingleInstance = 'Yes' - Type = 'NodeMajority' - Resource = '' - } - - Mock -CommandName 'Get-ClusterQuorum' -MockWith { - [PSCustomObject] @{ - Cluster = 'CLUSTER01' - QuorumType = 'Majority' - QuorumResource = $null - } - } - - Mock -CommandName 'Set-ClusterQuorum' -ParameterFilter { $NoWitness -eq $true } -MockWith { - } - - Context 'Validate Get-TargetResource method' { - - It 'Returns current configuration' { - - $Result = Get-TargetResource @TestParameter - - $Result.IsSingleInstance | Should Be $TestParameter.IsSingleInstance - $Result.Type | Should Be $TestParameter.Type - $Result.Resource | Should Be $TestParameter.Resource - } - } - - Context 'Validate Set-TargetResource method' { - - It 'Set the new configuration' { - - $Result = Set-TargetResource @TestParameter - - Assert-MockCalled -CommandName 'Set-ClusterQuorum' -ParameterFilter { $NoWitness -eq $true } -Times 1 - } - } - - Context 'Validate Test-TargetResource method' { - - It 'Check the current configuration' { - - $Result = Test-TargetResource @TestParameter - - $Result | Should Be $true - } - } - } -} - - -## Test NodeAndDiskMajority quorum type - -Describe 'xClusterQuorum (NodeAndDiskMajority / WS2012R2)' { - - InModuleScope $ModuleName { - - $TestParameter = @{ - IsSingleInstance = 'Yes' - Type = 'NodeAndDiskMajority' - Resource = 'Witness' - } - - Mock -CommandName 'Get-ClusterQuorum' -MockWith { - [PSCustomObject] @{ - Cluster = 'CLUSTER01' - QuorumType = 'NodeAndDiskMajority' - QuorumResource = [PSCustomObject] @{ - Name = 'Witness' - OwnerGroup = 'Cluster Group' - ResourceType = [PSCustomObject] @{ - DisplayName = 'Physical Disk' - } - } - } - } - - Mock -CommandName 'Set-ClusterQuorum' -ParameterFilter { $DiskWitness -eq 'Witness' } -MockWith { - } - - Context 'Validate Get-TargetResource method' { - - It 'Returns current configuration' { - - $Result = Get-TargetResource @TestParameter - - $Result.IsSingleInstance | Should Be $TestParameter.IsSingleInstance - $Result.Type | Should Be $TestParameter.Type - $Result.Resource | Should Be $TestParameter.Resource - } - } - - Context 'Validate Set-TargetResource method' { - - It 'Set the new configuration' { - - $Result = Set-TargetResource @TestParameter - - Assert-MockCalled -CommandName 'Set-ClusterQuorum' -ParameterFilter { $DiskWitness -eq 'Witness' } -Times 1 - } - } - - Context 'Validate Test-TargetResource method' { - - It 'Check the current configuration' { - - $Result = Test-TargetResource @TestParameter - - $Result | Should Be $true - } - } - } -} - -Describe 'xClusterQuorum (NodeAndDiskMajority / WS2016Prev)' { - - InModuleScope $ModuleName { - - $TestParameter = @{ - IsSingleInstance = 'Yes' - Type = 'NodeAndDiskMajority' - Resource = 'Witness' - } - - Mock -CommandName 'Get-ClusterQuorum' -MockWith { - [PSCustomObject] @{ - Cluster = 'CLUSTER01' - QuorumType = 'Majority' - QuorumResource = [PSCustomObject] @{ - Name = 'Witness' - OwnerGroup = 'Cluster Group' - ResourceType = [PSCustomObject] @{ - DisplayName = 'Physical Disk' - } - } - } - } - - Mock -CommandName 'Set-ClusterQuorum' -ParameterFilter { $DiskWitness -eq 'Witness' } -MockWith { - } - - Context 'Validate Get-TargetResource method' { - - It 'Returns current configuration' { - - $Result = Get-TargetResource @TestParameter - - $Result.IsSingleInstance | Should Be $TestParameter.IsSingleInstance - $Result.Type | Should Be $TestParameter.Type - $Result.Resource | Should Be $TestParameter.Resource - } - } - - Context 'Validate Set-TargetResource method' { - - It 'Set the new configuration' { - - $Result = Set-TargetResource @TestParameter - - Assert-MockCalled -CommandName 'Set-ClusterQuorum' -ParameterFilter { $DiskWitness -eq 'Witness' } -Times 1 - } - } - - Context 'Validate Test-TargetResource method' { - - It 'Check the current configuration' { - - $Result = Test-TargetResource @TestParameter - - $Result | Should Be $true - } - } - } -} - - -## Test NodeAndFileShareMajority quorum type - -Describe 'xClusterQuorum (NodeAndFileShareMajority / WS2012R2)' { - - InModuleScope $ModuleName { - - $TestParameter = @{ - IsSingleInstance = 'Yes' - Type = 'NodeAndFileShareMajority' - Resource = '\\FILE01\CLUSTER01' - } - - Mock -CommandName 'Get-ClusterQuorum' -MockWith { - [PSCustomObject] @{ - Cluster = 'CLUSTER01' - QuorumType = 'NodeAndFileShareMajority' - QuorumResource = [PSCustomObject] @{ - Name = 'File Share Witness' - OwnerGroup = 'Cluster Group' - ResourceType = [PSCustomObject] @{ - DisplayName = 'File Share Witness' - } - } - } - } - - Mock -CommandName 'Get-ClusterParameter' -ParameterFilter { $Name -eq 'SharePath' } -MockWith { - @( - [PSCustomObject] @{ - ClusterObject = 'File Share Witness' - Name = 'SharePath' - IsReadOnly = 'False' - ParameterType = 'String' - Value = '\\FILE01\CLUSTER01' - } - ) - } - - Mock -CommandName 'Set-ClusterQuorum' -ParameterFilter { $FileShareWitness -eq '\\FILE01\CLUSTER01' } -MockWith { - } - - Context 'Validate Get-TargetResource method' { - - It 'Returns current configuration' { - - $Result = Get-TargetResource @TestParameter - - $Result.IsSingleInstance | Should Be $TestParameter.IsSingleInstance - $Result.Type | Should Be $TestParameter.Type - $Result.Resource | Should Be $TestParameter.Resource - } - } - - Context 'Validate Set-TargetResource method' { - - It 'Set the new configuration' { - - $Result = Set-TargetResource @TestParameter - - Assert-MockCalled -CommandName 'Set-ClusterQuorum' -ParameterFilter { $FileShareWitness -eq '\\FILE01\CLUSTER01' } -Times 1 - } - } - - Context 'Validate Test-TargetResource method' { - - It 'Check the current configuration' { - - $Result = Test-TargetResource @TestParameter - - $Result | Should Be $true - } - } - } -} - -Describe 'xClusterQuorum (NodeAndFileShareMajority / WS2016Prev)' { - - InModuleScope $ModuleName { - - $TestParameter = @{ - IsSingleInstance = 'Yes' - Type = 'NodeAndFileShareMajority' - Resource = '\\FILE01\CLUSTER01' - } - - Mock -CommandName 'Get-ClusterQuorum' -MockWith { - [PSCustomObject] @{ - Cluster = 'CLUSTER01' - QuorumType = 'Majority' - QuorumResource = [PSCustomObject] @{ - Name = 'File Share Witness' - OwnerGroup = 'Cluster Group' - ResourceType = [PSCustomObject] @{ - DisplayName = 'File Share Witness' - } - } - } - } - - Mock -CommandName 'Get-ClusterParameter' -ParameterFilter { $Name -eq 'SharePath' } -MockWith { - @( - [PSCustomObject] @{ - ClusterObject = 'File Share Witness' - Name = 'SharePath' - IsReadOnly = 'False' - ParameterType = 'String' - Value = '\\FILE01\CLUSTER01' - } - ) - } - - Mock -CommandName 'Set-ClusterQuorum' -ParameterFilter { $FileShareWitness -eq '\\FILE01\CLUSTER01' } -MockWith { - } - - Context 'Validate Get-TargetResource method' { - - It 'Returns current configuration' { - - $Result = Get-TargetResource @TestParameter - - $Result.IsSingleInstance | Should Be $TestParameter.IsSingleInstance - $Result.Type | Should Be $TestParameter.Type - $Result.Resource | Should Be $TestParameter.Resource - } - } - - Context 'Validate Set-TargetResource method' { - - It 'Set the new configuration' { - - $Result = Set-TargetResource @TestParameter - - Assert-MockCalled -CommandName 'Set-ClusterQuorum' -ParameterFilter { $FileShareWitness -eq '\\FILE01\CLUSTER01' } -Times 1 - } - } - - Context 'Validate Test-TargetResource method' { - - It 'Check the current configuration' { - - $Result = Test-TargetResource @TestParameter - - $Result | Should Be $true - } - } - } -} - - -## Test DiskOnly quorum type - -Describe 'xClusterQuorum (NodeAndDiskMajority / WS2012R2)' { - - InModuleScope $ModuleName { - - $TestParameter = @{ - IsSingleInstance = 'Yes' - Type = 'DiskOnly' - Resource = 'Witness' - } - - Mock -CommandName 'Get-ClusterQuorum' -MockWith { - [PSCustomObject] @{ - Cluster = 'CLUSTER01' - QuorumType = 'DiskOnly' - QuorumResource = [PSCustomObject] @{ - Name = 'Witness' - OwnerGroup = 'Cluster Group' - ResourceType = [PSCustomObject] @{ - DisplayName = 'Physical Disk' - } - } - } - } - - Mock -CommandName 'Set-ClusterQuorum' -ParameterFilter { $DiskOnly -eq 'Witness' } -MockWith { - } - - Context 'Validate Get-TargetResource method' { - - It 'Returns current configuration' { - - $Result = Get-TargetResource @TestParameter - - $Result.IsSingleInstance | Should Be $TestParameter.IsSingleInstance - $Result.Type | Should Be $TestParameter.Type - $Result.Resource | Should Be $TestParameter.Resource - } - } - - Context 'Validate Set-TargetResource method' { - - It 'Set the new configuration' { - - $Result = Set-TargetResource @TestParameter - - Assert-MockCalled -CommandName 'Set-ClusterQuorum' -ParameterFilter { $DiskOnly -eq 'Witness' } -Times 1 - } - } - - Context 'Validate Test-TargetResource method' { - - It 'Check the current configuration' { - - $Result = Test-TargetResource @TestParameter - - $Result | Should Be $true - } - } - } -} - -Describe 'xClusterQuorum (NodeAndDiskMajority / WS2016Prev)' { - - InModuleScope $ModuleName { - - $TestParameter = @{ - IsSingleInstance = 'Yes' - Type = 'DiskOnly' - Resource = 'Witness' - } - - Mock -CommandName 'Get-ClusterQuorum' -MockWith { - [PSCustomObject] @{ - Cluster = 'CLUSTER01' - QuorumType = 'DiskOnly' - QuorumResource = [PSCustomObject] @{ - Name = 'Witness' - OwnerGroup = 'Cluster Group' - ResourceType = [PSCustomObject] @{ - DisplayName = 'Physical Disk' - } - } - } - } - - Mock -CommandName 'Set-ClusterQuorum' -ParameterFilter { $DiskOnly -eq 'Witness' } -MockWith { - } - - Context 'Validate Get-TargetResource method' { - - It 'Returns current configuration' { - - $Result = Get-TargetResource @TestParameter - - $Result.IsSingleInstance | Should Be $TestParameter.IsSingleInstance - $Result.Type | Should Be $TestParameter.Type - $Result.Resource | Should Be $TestParameter.Resource - } - } - - Context 'Validate Set-TargetResource method' { - - It 'Set the new configuration' { - - $Result = Set-TargetResource @TestParameter - - Assert-MockCalled -CommandName 'Set-ClusterQuorum' -ParameterFilter { $DiskOnly -eq 'Witness' } -Times 1 - } - } - - Context 'Validate Test-TargetResource method' { - - It 'Check the current configuration' { - - $Result = Test-TargetResource @TestParameter - - $Result | Should Be $true - } - } - } -} - diff --git a/Tests/TestHelpers/CommonTestHelper.psm1 b/Tests/TestHelpers/CommonTestHelper.psm1 new file mode 100644 index 0000000..7c56f3b --- /dev/null +++ b/Tests/TestHelpers/CommonTestHelper.psm1 @@ -0,0 +1,210 @@ +<# + .SYNOPSIS + Returns an invalid argument exception object + + .PARAMETER Message + The message explaining why this error is being thrown + + .PARAMETER ArgumentName + The name of the invalid argument that is causing this error to be thrown +#> +function Get-InvalidArgumentRecord +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ArgumentName + ) + + $argumentException = New-Object -TypeName 'ArgumentException' ` + -ArgumentList @( + $Message, + $ArgumentName + ) + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( + $argumentException, + $ArgumentName, + 'InvalidArgument', + $null + ) + } + + return New-Object @newObjectParameters +} + +<# + .SYNOPSIS + Returns an invalid operation exception object + + .PARAMETER Message + The message explaining why this error is being thrown + + .PARAMETER ErrorRecord + The error record containing the exception that is causing this terminating error +#> +function Get-InvalidOperationRecord +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter()] + [ValidateNotNull()] + [System.Management.Automation.ErrorRecord] + $ErrorRecord + ) + + if ($null -eq $ErrorRecord) + { + $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` + -ArgumentList @( $Message ) + } + else + { + $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` + -ArgumentList @( + $Message, + $ErrorRecord.Exception + ) + } + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( + $invalidOperationException.ToString(), + 'MachineStateIncorrect', + 'InvalidOperation', + $null + ) + } + + return New-Object @newObjectParameters +} + +<# + .SYNOPSIS + Returns an object not found exception object + + .PARAMETER Message + The message explaining why this error is being thrown + + .PARAMETER ErrorRecord + The error record containing the exception that is causing this terminating error +#> +function Get-ObjectNotFoundException +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter()] + [ValidateNotNull()] + [System.Management.Automation.ErrorRecord] + $ErrorRecord + ) + + if ($null -eq $ErrorRecord) + { + $objectNotFoundException = New-Object -TypeName 'System.Exception' ` + -ArgumentList @($Message) + } + else + { + $objectNotFoundException = New-Object -TypeName 'System.Exception' ` + -ArgumentList @( + $Message, + $ErrorRecord.Exception + ) + } + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( + $objectNotFoundException.ToString(), + 'MachineStateIncorrect', + 'ObjectNotFound', + $null + ) + } + + return New-Object @newObjectParameters +} + +<# + .SYNOPSIS + Returns an invalid result exception object + + .PARAMETER Message + The message explaining why this error is being thrown + + .PARAMETER ErrorRecord + The error record containing the exception that is causing this terminating error +#> +function Get-InvalidResultException +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter()] + [ValidateNotNull()] + [System.Management.Automation.ErrorRecord] + $ErrorRecord + ) + + if ($null -eq $ErrorRecord) + { + $exception = New-Object -TypeName 'System.Exception' ` + -ArgumentList @($Message) + } + else + { + $exception = New-Object -TypeName 'System.Exception' ` + -ArgumentList @( + $Message, + $ErrorRecord.Exception + ) + } + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( + $exception.ToString(), + 'MachineStateIncorrect', + 'InvalidResult', + $null + ) + } + + return New-Object @newObjectParameters +} + +Export-ModuleMember -Function @( + 'Get-InvalidArgumentRecord' + 'Get-InvalidOperationRecord' + 'Get-ObjectNotFoundException' + 'Get-InvalidResultException' +) diff --git a/Tests/Unit/CommonResourceHelper.Tests.ps1 b/Tests/Unit/CommonResourceHelper.Tests.ps1 new file mode 100644 index 0000000..284b579 --- /dev/null +++ b/Tests/Unit/CommonResourceHelper.Tests.ps1 @@ -0,0 +1,191 @@ +Describe 'CommonResourceHelper Unit Tests' { + BeforeAll { + # Import the CommonResourceHelper module to test + $dscResourcesFolderFilePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'DscResources' + + Import-Module -Name (Join-Path -Path $dscResourcesFolderFilePath ` + -ChildPath 'CommonResourceHelper.psm1') -Force + } + + InModuleScope 'CommonResourceHelper' { + Describe 'Get-LocalizedData' { + $mockTestPath = { + return $mockTestPathReturnValue + } + + $mockImportLocalizedData = { + $BaseDirectory | Should Be $mockExpectedLanguagePath + } + + BeforeEach { + Mock -CommandName Test-Path -MockWith $mockTestPath -Verifiable + Mock -CommandName Import-LocalizedData -MockWith $mockImportLocalizedData -Verifiable + } + + Context 'When loading localized data for Swedish' { + $mockExpectedLanguagePath = 'sv-SE' + $mockTestPathReturnValue = $true + + It 'Should call Import-LocalizedData with sv-SE language' { + Mock -CommandName Join-Path -MockWith { + return 'sv-SE' + } -Verifiable + + { Get-LocalizedData -ResourceName 'DummyResource' } | Should Not Throw + + Assert-MockCalled -CommandName Join-Path -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It + } + + $mockExpectedLanguagePath = 'en-US' + $mockTestPathReturnValue = $false + + It 'Should call Import-LocalizedData and fallback to en-US if sv-SE language does not exist' { + Mock -CommandName Join-Path -MockWith { + return $ChildPath + } -Verifiable + + { Get-LocalizedData -ResourceName 'DummyResource' } | Should Not Throw + + Assert-MockCalled -CommandName Join-Path -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It + } + + Context 'When $ScriptRoot is set to a path' { + $mockExpectedLanguagePath = 'sv-SE' + $mockTestPathReturnValue = $true + + It 'Should call Import-LocalizedData with sv-SE language' { + Mock -CommandName Join-Path -MockWith { + return 'sv-SE' + } -Verifiable + + { Get-LocalizedData -ResourceName 'DummyResource' -ScriptRoot '.' } | Should Not Throw + + Assert-MockCalled -CommandName Join-Path -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It + } + + $mockExpectedLanguagePath = 'en-US' + $mockTestPathReturnValue = $false + + It 'Should call Import-LocalizedData and fallback to en-US if sv-SE language does not exist' { + Mock -CommandName Join-Path -MockWith { + return $ChildPath + } -Verifiable + + { Get-LocalizedData -ResourceName 'DummyResource' -ScriptRoot '.' } | Should Not Throw + + Assert-MockCalled -CommandName Join-Path -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It + } + } + } + + Context 'When loading localized data for English' { + Mock -CommandName Join-Path -MockWith { + return 'en-US' + } -Verifiable + + $mockExpectedLanguagePath = 'en-US' + $mockTestPathReturnValue = $true + + It 'Should call Import-LocalizedData with en-US language' { + { Get-LocalizedData -ResourceName 'DummyResource' } | Should Not Throw + } + } + + Assert-VerifiableMocks + } + + Describe 'New-InvalidResultException' { + Context 'When calling with Message parameter only' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + + { New-InvalidResultException -Message $mockErrorMessage } | Should Throw $mockErrorMessage + } + } + + Context 'When calling with both the Message and ErrorRecord parameter' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + $mockExceptionErrorMessage = 'Mocked exception error message' + + $mockException = New-Object System.Exception $mockExceptionErrorMessage + $mockErrorRecord = New-Object System.Management.Automation.ErrorRecord $mockException, $null, 'InvalidResult', $null + + { New-InvalidResultException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should Throw ('System.Exception: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage) + } + } + + Assert-VerifiableMocks + } + + Describe 'New-ObjectNotFoundException' { + Context 'When calling with Message parameter only' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + + { New-ObjectNotFoundException -Message $mockErrorMessage } | Should Throw $mockErrorMessage + } + } + + Context 'When calling with both the Message and ErrorRecord parameter' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + $mockExceptionErrorMessage = 'Mocked exception error message' + + $mockException = New-Object System.Exception $mockExceptionErrorMessage + $mockErrorRecord = New-Object System.Management.Automation.ErrorRecord $mockException, $null, 'InvalidResult', $null + + { New-ObjectNotFoundException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should Throw ('System.Exception: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage) + } + } + + Assert-VerifiableMocks + } + + Describe 'New-InvalidOperationException' { + Context 'When calling with Message parameter only' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + + { New-InvalidOperationException -Message $mockErrorMessage } | Should Throw $mockErrorMessage + } + } + + Context 'When calling with both the Message and ErrorRecord parameter' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + $mockExceptionErrorMessage = 'Mocked exception error message' + + $mockException = New-Object System.Exception $mockExceptionErrorMessage + $mockErrorRecord = New-Object System.Management.Automation.ErrorRecord $mockException, $null, 'InvalidResult', $null + + { New-InvalidOperationException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should Throw ('System.InvalidOperationException: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage) + } + } + + Assert-VerifiableMocks + } + + Describe 'New-InvalidArgumentException' { + Context 'When calling with both the Message and ArgumentName parameter' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + $mockArgumentName = 'MockArgument' + + { New-InvalidArgumentException -Message $mockErrorMessage -ArgumentName $mockArgumentName } | Should Throw ('Parameter name: {0}' -f $mockArgumentName) + } + } + + Assert-VerifiableMocks + } + } +} diff --git a/Tests/Unit/MSFT_xCluster.Tests.ps1 b/Tests/Unit/MSFT_xCluster.Tests.ps1 index 8aa5cd7..f13482e 100644 --- a/Tests/Unit/MSFT_xCluster.Tests.ps1 +++ b/Tests/Unit/MSFT_xCluster.Tests.ps1 @@ -1,4 +1,11 @@ -$script:DSCModuleName = 'xFailOverCluster' +<# + Suppressing this rule because a plain text password variable is used to mock the LogonUser static + method and is required for the tests. +#> +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] +param() + +$script:DSCModuleName = 'xFailOverCluster' $script:DSCResourceName = 'MSFT_xCluster' #region HEADER @@ -23,6 +30,7 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'FailoverClusters.stubs.psm1') -Global -Force + Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'TestHelpers') -ChildPath 'CommonTestHelper.psm1') -Global -Force } function Invoke-TestCleanup @@ -45,15 +53,15 @@ try $mockClusterName = 'CLUSTER001' $mockStaticIpAddress = '192.168.10.10' - $mockGetWmiObject = { + $mockGetCimInstance = { return [PSCustomObject] @{ Domain = $mockDynamicDomainName Name = $mockDynamicServerName } } - $mockGetWmiObject_ParameterFilter = { - $Class -eq 'Win32_ComputerSystem' + $mockGetCimInstance_ParameterFilter = { + $ClassName -eq 'Win32_ComputerSystem' } $mockGetCluster = { @@ -96,21 +104,68 @@ try ) } + $mockNewObjectWindowsIdentity = { + return [PSCustomObject] @{} | + Add-Member -MemberType ScriptMethod -Name Impersonate -Value { + return [PSCustomObject] @{} | + Add-Member -MemberType ScriptMethod -Name Undo -Value {} -PassThru | + Add-Member -MemberType ScriptMethod -Name Dispose -Value {} -PassThru -Force + } -PassThru -Force + } + + $mockNewObjectWindowsIdentity_ParameterFilter = { + $TypeName -eq 'Security.Principal.WindowsIdentity' + } + $mockDefaultParameters = @{ Name = $mockClusterName StaticIPAddress = $mockStaticIpAddress DomainAdministratorCredential = $mockAdministratorCredential } + class MockLibImpersonation + { + static [bool] $ReturnValue = $false + + static [bool]LogonUser( + [string] $userName, + [string] $domain, + [string] $password, + [int] $logonType, + [int] $logonProvider, + [ref] $token + ) + { + return [MockLibImpersonation]::ReturnValue + } + + static [bool]CloseHandle([System.IntPtr]$Token) + { + return [MockLibImpersonation]::ReturnValue + } + } + + [MockLibImpersonation]::ReturnValue = $true + $mockLibImpersonationObject = [MockLibImpersonation]::New() + Describe 'xCluster\Get-TargetResource' { + BeforeAll { + Mock -CommandName Add-Type -MockWith { + return $mockLibImpersonationObject + } + + Mock -CommandName New-Object -MockWith $mockNewObjectWindowsIdentity -ParameterFilter $mockNewObjectWindowsIdentity_ParameterFilter -Verifiable + } + Context 'When the computers domain name cannot be evaluated' { It 'Should throw the correct error message' { $mockDynamicDomainName = $null $mockDynamicServerName = $mockServerName - Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockGetCimInstance_ParameterFilter -Verifiable - { Get-TargetResource @mockDefaultParameters } | Should -Throw "Can't find machine's domain name" + $mockCorrectErrorRecord = Get-InvalidOperationRecord -Message $script:localizedData.TargetNodeDomainMissing + { Get-TargetResource @mockDefaultParameters } | Should Throw $mockCorrectErrorRecord } } @@ -120,15 +175,16 @@ try $mockDynamicServerName = $mockServerName Mock -CommandName Get-Cluster -Verifiable - Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockGetCimInstance_ParameterFilter -Verifiable - { Get-TargetResource @mockDefaultParameters } | Should -Throw ("Can't find the cluster {0}" -f $mockClusterName) + $mockCorrectErrorRecord = Get-ObjectNotFoundException -Message ($script:localizedData.ClusterNameNotFound -f $mockClusterName) + { Get-TargetResource @mockDefaultParameters } | Should Throw $mockCorrectErrorRecord } } Context 'When the system is not in the desired state' { BeforeEach { - Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockGetCimInstance_ParameterFilter -Verifiable Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable Mock -CommandName Get-ClusterGroup -MockWith $mockGetClusterGroup -ParameterFilter $mockGetClusterGroup_ParameterFilter -Verifiable Mock -CommandName Get-ClusterParameter -MockWith $mockGetClusterParameter -Verifiable @@ -153,14 +209,23 @@ try } Describe 'xCluster\Set-TargetResource' { + BeforeAll { + Mock -CommandName Add-Type -MockWith { + return $mockLibImpersonationObject + } + + Mock -CommandName New-Object -MockWith $mockNewObjectWindowsIdentity -ParameterFilter $mockNewObjectWindowsIdentity_ParameterFilter -Verifiable + } + Context 'When computers domain name cannot be evaluated' { It 'Should throw the correct error message' { $mockDynamicDomainName = $null $mockDynamicServerName = $mockServerName - Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockGetCimInstance_ParameterFilter -Verifiable - { Set-TargetResource @mockDefaultParameters } | Should -Throw "Can't find machine's domain name" + $mockCorrectErrorRecord = Get-InvalidOperationRecord -Message $script:localizedData.TargetNodeDomainMissing + { Set-TargetResource @mockDefaultParameters } | Should Throw $mockCorrectErrorRecord } } @@ -169,7 +234,7 @@ try Mock -CommandName New-Cluster -Verifiable Mock -CommandName Remove-ClusterNode -Verifiable Mock -CommandName Add-ClusterNode -Verifiable - Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockGetCimInstance_ParameterFilter -Verifiable } $mockDynamicDomainName = $mockDomainName @@ -184,7 +249,7 @@ try # This is used to evaluate that cluster do exists after New-Cluster cmdlet has been run. Mock -CommandName Get-Cluster -MockWith $mockGetCluster - { Set-TargetResource @mockDefaultParameters } | Should -Not -Throw + { Set-TargetResource @mockDefaultParameters } | Should Not Throw Assert-MockCalled -CommandName New-Cluster -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It @@ -202,7 +267,7 @@ try # This is used to evaluate that cluster do exists after New-Cluster cmdlet has been run. Mock -CommandName Get-Cluster -MockWith $mockGetCluster - { Set-TargetResource @mockDefaultParameters } | Should -Not -Throw + { Set-TargetResource @mockDefaultParameters } | Should Not Throw Assert-MockCalled -CommandName New-Cluster -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It @@ -215,7 +280,8 @@ try It 'Should throw the correct error message' { Mock -CommandName Get-Cluster - { Set-TargetResource @mockDefaultParameters } | Should -Throw 'Cluster creation failed. Please verify output of ''Get-Cluster'' command' + $mockCorrectErrorRecord = Get-InvalidOperationRecord -Message $script:localizedData.FailedCreatingCluster + { Set-TargetResource @mockDefaultParameters } | Should Throw $mockCorrectErrorRecord Assert-MockCalled -CommandName New-Cluster -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It @@ -228,7 +294,7 @@ try Mock -CommandName Get-ClusterNode Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter - { Set-TargetResource @mockDefaultParameters } | Should -Not -Throw + { Set-TargetResource @mockDefaultParameters } | Should Not Throw Assert-MockCalled -CommandName New-Cluster -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It @@ -246,7 +312,7 @@ try It 'Should call both Remove-ClusterNode and Add-ClusterNode cmdlet' { Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter - { Set-TargetResource @mockDefaultParameters } | Should -Not -Throw + { Set-TargetResource @mockDefaultParameters } | Should Not Throw Assert-MockCalled -CommandName New-Cluster -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 1 -Scope It @@ -261,7 +327,7 @@ try Mock -CommandName New-Cluster -Verifiable Mock -CommandName Remove-ClusterNode -Verifiable Mock -CommandName Add-ClusterNode -Verifiable - Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockGetCimInstance_ParameterFilter -Verifiable Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable Mock -CommandName Get-ClusterParameter -MockWith $mockGetClusterParameter -Verifiable @@ -283,7 +349,7 @@ try It 'Should not call any of the cluster cmdlets' -Skip { Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable - { Set-TargetResource @mockDefaultParameters } | Should -Not -Throw + { Set-TargetResource @mockDefaultParameters } | Should Not Throw Assert-MockCalled -CommandName New-Cluster -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Remove-ClusterNode -Exactly -Times 0 -Scope It @@ -296,20 +362,29 @@ try } Describe 'xCluster\Test-TargetResource' { + BeforeAll { + Mock -CommandName Add-Type -MockWith { + return $mockLibImpersonationObject + } + + Mock -CommandName New-Object -MockWith $mockNewObjectWindowsIdentity -ParameterFilter $mockNewObjectWindowsIdentity_ParameterFilter -Verifiable + } + Context 'When computers domain name cannot be evaluated' { It 'Should throw the correct error message' { $mockDynamicDomainName = $null $mockDynamicServerName = $mockServerName - Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockGetCimInstance_ParameterFilter -Verifiable - { Test-TargetResource @mockDefaultParameters } | Should -Throw "Can't find machine's domain name" + $mockCorrectErrorRecord = Get-InvalidOperationRecord -Message $script:localizedData.TargetNodeDomainMissing + { Test-TargetResource @mockDefaultParameters } | Should Throw $mockCorrectErrorRecord } } Context 'When the system is not in the desired state' { BeforeEach { - Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockGetCimInstance_ParameterFilter -Verifiable } $mockDynamicDomainName = $mockDomainName @@ -320,7 +395,7 @@ try Mock -CommandName Get-Cluster -Verifiable $testTargetResourceResult = Test-TargetResource @mockDefaultParameters - $testTargetResourceResult | Should -Be $false + $testTargetResourceResult | Should Be $false } Assert-VerifiableMocks @@ -333,7 +408,7 @@ try } -Verifiable $testTargetResourceResult = Test-TargetResource @mockDefaultParameters - $testTargetResourceResult | Should -Be $false + $testTargetResourceResult | Should Be $false } Assert-VerifiableMocks @@ -346,7 +421,7 @@ try $testTargetResourceResult = Test-TargetResource @mockDefaultParameters - $testTargetResourceResult | Should -Be $false + $testTargetResourceResult | Should Be $false } Assert-VerifiableMocks @@ -363,7 +438,7 @@ try It 'Should return $false' { $testTargetResourceResult = Test-TargetResource @mockDefaultParameters - $testTargetResourceResult | Should -Be $false + $testTargetResourceResult | Should Be $false } Assert-VerifiableMocks @@ -374,7 +449,7 @@ try Context 'When the system is in the desired state' { BeforeEach { Mock -CommandName Get-ClusterNode -MockWith $mockGetClusterNode -Verifiable - Mock -CommandName Get-WmiObject -MockWith $mockGetWmiObject -ParameterFilter $mockGetWmiObject_ParameterFilter -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockGetCimInstance_ParameterFilter -Verifiable Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable } @@ -385,13 +460,44 @@ try Context 'When the node already exist' { It 'Should return $true' { $testTargetResourceResult = Test-TargetResource @mockDefaultParameters - $testTargetResourceResult | Should -Be $true + $testTargetResourceResult | Should Be $true } Assert-VerifiableMocks } } } + + [MockLibImpersonation]::ReturnValue = $false + $mockLibImpersonationObject = [MockLibImpersonation]::New() + + Describe 'xCluster\Set-ImpersonateAs' -Tag 'Helper' { + Context 'When impersonating credentials fails' { + It 'Should throw the correct error message' { + Mock -CommandName Add-Type -MockWith { + return $mockLibImpersonationObject + } + + $mockCorrectErrorRecord = Get-InvalidOperationRecord -Message ($script:localizedData.UnableToImpersonateUser -f $mockAdministratorCredential.GetNetworkCredential().UserName) + { Set-ImpersonateAs -Credential $mockAdministratorCredential } | Should Throw $mockCorrectErrorRecord + } + } + } + + Describe 'xCluster\Close-UserToken' -Tag 'Helper' { + Context 'When closing user token fails' { + It 'Should throw the correct error message' { + Mock -CommandName Add-Type -MockWith { + return $mockLibImpersonationObject + } -Verifiable + + $mockToken = [System.IntPtr]::New(12345) + + $mockCorrectErrorRecord = Get-InvalidOperationRecord -Message ($script:localizedData.UnableToCloseToken -f $mockToken.ToString()) + { Close-UserToken -Token $mockToken } | Should Throw $mockCorrectErrorRecord + } + } + } } } finally diff --git a/Tests/Unit/MSFT_xClusterDisk.Tests.ps1 b/Tests/Unit/MSFT_xClusterDisk.Tests.ps1 index 12c9d28..faa8806 100644 --- a/Tests/Unit/MSFT_xClusterDisk.Tests.ps1 +++ b/Tests/Unit/MSFT_xClusterDisk.Tests.ps1 @@ -1,4 +1,4 @@ -$script:DSCModuleName = 'xFailOverCluster' +$script:DSCModuleName = 'xFailOverCluster' $script:DSCResourceName = 'MSFT_xClusterDisk' #region Header diff --git a/Tests/Unit/MSFT_xClusterPreferredOwner.Tests.ps1 b/Tests/Unit/MSFT_xClusterPreferredOwner.Tests.ps1 index 3543b73..f469b64 100644 --- a/Tests/Unit/MSFT_xClusterPreferredOwner.Tests.ps1 +++ b/Tests/Unit/MSFT_xClusterPreferredOwner.Tests.ps1 @@ -1,4 +1,4 @@ -$script:DSCModuleName = 'xFailOverCluster' +$script:DSCModuleName = 'xFailOverCluster' $script:DSCResourceName = 'MSFT_xClusterPreferredOwner' #region Header diff --git a/Tests/Unit/MSFT_xClusterQuorum.Tests.ps1 b/Tests/Unit/MSFT_xClusterQuorum.Tests.ps1 new file mode 100644 index 0000000..885be0f --- /dev/null +++ b/Tests/Unit/MSFT_xClusterQuorum.Tests.ps1 @@ -0,0 +1,609 @@ +$script:DSCModuleName = 'xFailOverCluster' +$script:DSCResourceName = 'MSFT_xClusterQuorum' + +#region Header + +# Unit Test Template Version: 1.2.0 +$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 + +#endregion Header + +function Invoke-TestSetup +{ + Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'FailoverClusters.stubs.psm1') -Global -Force +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + $mockQuorumType_Majority = 'Majority' + $mockQuorumType_NodeMajority = 'NodeMajority' + $mockQuorumType_NodeAndDiskMajority = 'NodeAndDiskMajority' + $mockQuorumType_NodeAndFileShareMajority = 'NodeAndFileShareMajority' + $mockQuorumType_DiskOnly = 'DiskOnly' + $mockQuorumType_Unknown = 'Unknown' + + $mockQuorumResourceName = 'Witness' + $mockQuorumFileShareWitnessPath = '\\FILE01\CLUSTER01' + + $mockGetClusterQuorum = { + $getClusterQuorumReturnValue = [PSCustomObject] @{ + Cluster = 'CLUSTER01' + QuorumType = $mockDynamicQuorumType + QuorumResource = [PSCustomObject] @{ + Name = $mockDynamicQuorumResourceName + OwnerGroup = 'Cluster Group' + ResourceType = [PSCustomObject] @{ + DisplayName = 'Physical Disk' + } + } + } + + switch ($mockDynamicExcpectedQuorumType) + { + $mockQuorumType_NodeMajority + { + $getClusterQuorumReturnValue.QuorumResource = $null + } + + $mockQuorumType_NodeAndDiskMajority + { + $getClusterQuorumReturnValue.QuorumResource.ResourceType.DisplayName = 'Physical Disk' + } + + $mockQuorumType_NodeAndFileShareMajority + { + $getClusterQuorumReturnValue.QuorumResource.ResourceType.DisplayName = 'File Share Witness' + } + + $mockQuorumType_Unknown + { + $getClusterQuorumReturnValue.QuorumResource.ResourceType.DisplayName = 'Unknown' + } + } + + $getClusterQuorumReturnValue + } + + $mockGetClusterParameter = { + @( + [PSCustomObject] @{ + ClusterObject = 'File Share Witness' + Name = 'SharePath' + IsReadOnly = 'False' + ParameterType = 'String' + Value = $mockQuorumFileShareWitnessPath + } + ) + } + + $mockGetClusterParameter_ParameterFilter = { + $Name -eq 'SharePath' + } + + $mockSetClusterQuorum_NoWitness_ParameterFilter = { + $NoWitness -eq $true + } + + $mockSetClusterQuorum_DiskWitness_ParameterFilter = { + $PSBoundParameters.ContainsKey('DiskWitness') -eq $true + } + + $mockSetClusterQuorum_FileShareWitness_ParameterFilter = { + $PSBoundParameters.ContainsKey('FileShareWitness') -eq $true + } + + $mockSetClusterQuorum_DiskOnly_ParameterFilter = { + $PSBoundParameters.ContainsKey('DiskOnly') -eq $true + } + + $mockSetClusterQuorum = { + $wrongParameters = $false + + # Evaluate if the Set-ClusterQuorum is called with the correct parameters. + switch ($mockDynamicSetClusterQuorum_ExcpectedQuorumType) + { + $mockQuorumType_NodeMajority + { + if (-not $NoWitness) + { + $wrongParameters = $true + } + } + + $mockQuorumType_NodeAndDiskMajority + { + if ($DiskWitness -ne $mockDynamicQuorumResourceName) + { + $wrongParameters = $true + } + } + + $mockQuorumType_NodeAndFileShareMajority + { + if ($FileShareWitness -ne $mockDynamicQuorumResourceName) + { + $wrongParameters = $true + } + } + + $mockQuorumType_DiskOnly + { + if ($DiskOnly -ne $mockDynamicQuorumResourceName) + { + $wrongParameters = $true + } + } + + default + { + $wrongParameters = $true + } + } + + if ($wrongParameters) + { + throw 'Mock Set-ClusterQuorum was called with the wrong parameters.' + } + } + + $mockDefaultParameters = @{ + IsSingleInstance = 'Yes' + } + + Describe 'xClusterQuorum\Get-TargetResource' { + BeforeEach { + Mock -CommandName 'Get-ClusterQuorum' -MockWith $mockGetClusterQuorum + Mock -CommandName 'Get-ClusterParameter' -MockWith $mockGetClusterParameter -ParameterFilter $mockGetClusterParameter_ParameterFilter + + $mockTestParameters = $mockDefaultParameters.Clone() + } + + Context 'When the system is either in the desired state or not in the desired state' { + BeforeAll { + $mockDynamicQuorumResourceName = $mockQuorumResourceName + } + + Context 'When quorum type should be NodeMajority' { + Context 'When target node is Windows Server 2012 R2' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_NodeMajority + } + + It 'Should return the correct type' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult | Should BeOfType [System.Collections.Hashtable] + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.IsSingleInstance | Should Be $mockTestParameters.IsSingleInstance + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.Type | Should Be $mockQuorumType_NodeMajority + $getTargetResourceResult.Resource | Should Be $mockQuorumResourceName + } + } + + Context 'When target node is Windows Server 2016 and newer' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_Majority + $mockDynamicExcpectedQuorumType = $mockQuorumType_NodeMajority + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.IsSingleInstance | Should Be $mockTestParameters.IsSingleInstance + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.Type | Should Be $mockQuorumType_NodeMajority + $getTargetResourceResult.Resource | Should BeNullorEmpty + } + } + } + + Context 'When desired state should be NodeAndDiskMajority' { + Context 'When target node is Windows Server 2012 R2' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_NodeAndDiskMajority + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.IsSingleInstance | Should Be $mockTestParameters.IsSingleInstance + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.Type | Should Be $mockQuorumType_NodeAndDiskMajority + $getTargetResourceResult.Resource | Should Be $mockQuorumResourceName + } + } + + Context 'When target node is Windows Server 2016 and newer' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_Majority + $mockDynamicExcpectedQuorumType = $mockQuorumType_NodeAndDiskMajority + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.IsSingleInstance | Should Be $mockTestParameters.IsSingleInstance + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.Type | Should Be $mockQuorumType_NodeAndDiskMajority + $getTargetResourceResult.Resource | Should Be $mockQuorumResourceName + } + } + } + + Context 'When desired state should be NodeAndFileShareMajority' { + Context 'When target node is Windows Server 2012 R2' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_NodeAndFileShareMajority + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.IsSingleInstance | Should Be $mockTestParameters.IsSingleInstance + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.Type | Should Be $mockQuorumType_NodeAndFileShareMajority + $getTargetResourceResult.Resource | Should Be $mockQuorumFileShareWitnessPath + } + } + + Context 'When target node is Windows Server 2016 and newer' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_Majority + $mockDynamicExcpectedQuorumType = $mockQuorumType_NodeAndFileShareMajority + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.IsSingleInstance | Should Be $mockTestParameters.IsSingleInstance + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.Type | Should Be $mockQuorumType_NodeAndFileShareMajority + $getTargetResourceResult.Resource | Should Be $mockQuorumFileShareWitnessPath + } + } + } + + Context 'When desired state should be DiskOnly' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_DiskOnly + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.IsSingleInstance | Should Be $mockTestParameters.IsSingleInstance + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @mockTestParameters + $getTargetResourceResult.Type | Should Be $mockQuorumType_DiskOnly + $getTargetResourceResult.Resource | Should Be $mockQuorumResourceName + } + } + + Context 'When quorum type is unknown' { + Context 'When target node is Windows Server 2012 R2' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_Unknown + } + + It 'Should throw the correct error message' { + { Get-TargetResource @mockTestParameters } | Should Throw ('Unknown quorum type: {0}' -f $mockQuorumType_Unknown) + } + } + + Context 'When target node is Windows Server 2016 and newer' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_Majority + $mockDynamicExcpectedQuorumType = $mockQuorumType_Unknown + } + + It 'Should throw the correct error message' { + { Get-TargetResource @mockTestParameters } | Should Throw ('Unknown quorum resource: {0}' -f '@{Name=Witness; OwnerGroup=Cluster Group; ResourceType=}') + } + } + } + } + } + + Describe 'xClusterQuorum\Test-TargetResource' { + BeforeEach { + Mock -CommandName 'Get-ClusterQuorum' -MockWith $mockGetClusterQuorum + Mock -CommandName 'Get-ClusterParameter' -MockWith $mockGetClusterParameter -ParameterFilter $mockGetClusterParameter_ParameterFilter + + $mockTestParameters = $mockDefaultParameters.Clone() + } + + Context 'When the system is not in the desired state' { + Context 'When quorum type should be NodeMajority' { + BeforeEach { + $mockTestParameters['Type'] = $mockQuorumType_NodeMajority + $mockTestParameters['Resource'] = $mockQuorumResourceName + } + + Context 'When target node is Windows Server 2012 R2' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_NodeAndDiskMajority + $mockDynamicQuorumResourceName = $mockQuorumResourceName + } + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockTestParameters + $testTargetResourceResult | Should Be $false + } + } + + Context 'When target node is Windows Server 2016 and newer' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_Majority + $mockDynamicQuorumResourceName = $mockQuorumResourceName + + $mockDynamicExcpectedQuorumType = $mockQuorumType_NodeAndDiskMajority + } + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockTestParameters + $testTargetResourceResult | Should Be $false + } + } + } + + Context 'When quorum type is NodeMajority but the resource is not in desired state' { + BeforeEach { + $mockTestParameters['Type'] = $mockQuorumType_NodeMajority + $mockTestParameters['Resource'] = $mockQuorumResourceName + } + + Context 'When target node is Windows Server 2012 R2' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_NodeMajority + $mockDynamicQuorumResourceName = 'Unknown' + } + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockTestParameters + $testTargetResourceResult | Should Be $false + } + } + + Context 'When target node is Windows Server 2016 and newer' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_Majority + + $mockDynamicExcpectedQuorumType = $mockQuorumType_NodeMajority + } + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockTestParameters + $testTargetResourceResult | Should Be $false + } + } + } + + Context 'When desired state should be NodeAndDiskMajority' { + Context 'When target node is Windows Server 2012 R2' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_NodeAndDiskMajority + } + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockTestParameters + $testTargetResourceResult | Should Be $false + } + } + + Context 'When target node is Windows Server 2016 and newer' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_Majority + $mockDynamicExcpectedQuorumType = $mockQuorumType_NodeAndDiskMajority + } + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockTestParameters + $testTargetResourceResult | Should Be $false + } + } + } + + Context 'When desired state should be NodeAndFileShareMajority' { + Context 'When target node is Windows Server 2012 R2' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_NodeAndFileShareMajority + } + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockTestParameters + $testTargetResourceResult | Should Be $false + } + } + + Context 'When target node is Windows Server 2016 and newer' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_Majority + $mockDynamicExcpectedQuorumType = $mockQuorumType_NodeAndFileShareMajority + } + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockTestParameters + $testTargetResourceResult | Should Be $false + } + } + } + + Context 'When desired state should be DiskOnly' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_DiskOnly + } + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockTestParameters + $testTargetResourceResult | Should Be $false + } + } + } + + Context 'When the system is in the desired state' { + Context 'When quorum type is NodeMajority but the resource is not in desired state' { + BeforeEach { + $mockTestParameters['Type'] = $mockQuorumType_NodeMajority + $mockTestParameters['Resource'] = $mockQuorumResourceName + } + + Context 'When target node is Windows Server 2012 R2' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_NodeMajority + $mockDynamicQuorumResourceName = $mockQuorumResourceName + } + + It 'Should return the value $true' { + $testTargetResourceResult = Test-TargetResource @mockTestParameters + $testTargetResourceResult | Should Be $true + } + } + + Context 'When target node is Windows Server 2016 and newer' { + BeforeEach { + $mockDynamicQuorumType = $mockQuorumType_Majority + $mockTestParameters['Resource'] = $null + + $mockDynamicExcpectedQuorumType = $mockQuorumType_NodeMajority + } + + It 'Should return the value $true' { + $testTargetResourceResult = Test-TargetResource @mockTestParameters + $testTargetResourceResult | Should Be $true + } + } + } + } + } + + Describe 'xClusterQuorum\Set-TargetResource' { + BeforeEach { + $mockTestParameters = $mockDefaultParameters.Clone() + } + + Context 'When quorum type should be NodeMajority' { + BeforeEach { + Mock -CommandName 'Set-ClusterQuorum' -MockWith $mockSetClusterQuorum ` + -ParameterFilter $mockSetClusterQuorum_NoWitness_ParameterFilter + + $mockTestParameters['Type'] = $mockQuorumType_NodeMajority + + $mockDynamicSetClusterQuorum_ExcpectedQuorumType = $mockQuorumType_NodeMajority + } + + It 'Should set the quorum in the cluster without throwing an error' { + { Set-TargetResource @mockTestParameters } | Should Not Throw + + Assert-MockCalled -CommandName 'Set-ClusterQuorum' ` + -ParameterFilter $mockSetClusterQuorum_NoWitness_ParameterFilter ` + -Exactly -Times 1 -Scope It + } + } + + Context 'When quorum type should be NodeMajority' { + BeforeEach { + Mock -CommandName 'Set-ClusterQuorum' -MockWith $mockSetClusterQuorum ` + -ParameterFilter $mockSetClusterQuorum_DiskWitness_ParameterFilter + + $mockTestParameters['Type'] = $mockQuorumType_NodeAndDiskMajority + $mockTestParameters['Resource'] = $mockQuorumResourceName + + $mockDynamicQuorumResourceName = $mockQuorumResourceName + $mockDynamicSetClusterQuorum_ExcpectedQuorumType = $mockQuorumType_NodeAndDiskMajority + } + + It 'Should set the quorum in the cluster without throwing an error' { + { Set-TargetResource @mockTestParameters } | Should Not Throw + + Assert-MockCalled -CommandName 'Set-ClusterQuorum' ` + -ParameterFilter $mockSetClusterQuorum_DiskWitness_ParameterFilter ` + -Exactly -Times 1 -Scope It + } + } + + Context 'When quorum type should be NodeMajority' { + BeforeEach { + Mock -CommandName 'Set-ClusterQuorum' -MockWith $mockSetClusterQuorum ` + -ParameterFilter $mockSetClusterQuorum_FileShareWitness_ParameterFilter + + $mockTestParameters['Type'] = $mockQuorumType_NodeAndFileShareMajority + $mockTestParameters['Resource'] = $mockQuorumResourceName + + $mockDynamicQuorumResourceName = $mockQuorumResourceName + $mockDynamicSetClusterQuorum_ExcpectedQuorumType = $mockQuorumType_NodeAndFileShareMajority + } + + It 'Should set the quorum in the cluster without throwing an error' { + { Set-TargetResource @mockTestParameters } | Should Not Throw + + Assert-MockCalled -CommandName 'Set-ClusterQuorum' ` + -ParameterFilter $mockSetClusterQuorum_FileShareWitness_ParameterFilter ` + -Exactly -Times 1 -Scope It + } + } + + Context 'When quorum type should be NodeMajority' { + BeforeEach { + Mock -CommandName 'Set-ClusterQuorum' -MockWith $mockSetClusterQuorum ` + -ParameterFilter $mockSetClusterQuorum_DiskOnly_ParameterFilter + + $mockTestParameters['Type'] = $mockQuorumType_DiskOnly + $mockTestParameters['Resource'] = $mockQuorumResourceName + + $mockDynamicQuorumResourceName = $mockQuorumResourceName + $mockDynamicSetClusterQuorum_ExcpectedQuorumType = $mockQuorumType_DiskOnly + } + + It 'Should set the quorum in the cluster without throwing an error' { + { Set-TargetResource @mockTestParameters } | Should Not Throw + + Assert-MockCalled -CommandName 'Set-ClusterQuorum' ` + -ParameterFilter $mockSetClusterQuorum_DiskOnly_ParameterFilter ` + -Exactly -Times 1 -Scope It + } + } + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/Tests/Unit/MSFT_xWaitForCluster.Tests.ps1 b/Tests/Unit/MSFT_xWaitForCluster.Tests.ps1 new file mode 100644 index 0000000..3497d85 --- /dev/null +++ b/Tests/Unit/MSFT_xWaitForCluster.Tests.ps1 @@ -0,0 +1,221 @@ +$script:DSCModuleName = 'xFailOverCluster' +$script:DSCResourceName = 'MSFT_xWaitForCluster' + +#region HEADER + +# Unit Test Template Version: 1.2.0 +$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 + +#endregion HEADER + +function Invoke-TestSetup +{ + Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'FailoverClusters.stubs.psm1') -Global -Force +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + $mockDomainName = 'domain.local' + $mockClusterName = 'CLUSTER001' + $mockRetryIntervalSec = '1' + $mockRetryCount = '1' + + $mockGetCimInstance = { + return [PSCustomObject] @{ + Domain = $mockDynamicDomainName + } + } + + $mockCimInstance_ParameterFilter = { + $ClassName -eq 'Win32_ComputerSystem' + } + + $mockGetCluster = { + return [PSCustomObject] @{ + Domain = $mockDomainName + Name = $mockClusterName + } + } + + $mockGetCluster_ParameterFilter = { + $Name -eq $mockClusterName -and $Domain -eq $mockDomainName + } + + $mockDefaultParameters = @{ + Name = $mockClusterName + RetryIntervalSec = $mockRetryIntervalSec + RetryCount = $mockRetryCount + } + + Describe 'xCluster\Get-TargetResource' -Tag Get { + Context 'When the system is either in the desired state or not in the desired state' { + It 'Returns a [System.Collection.Hashtable] type' { + $getTargetResourceResult = Get-TargetResource @mockDefaultParameters + $getTargetResourceResult | Should BeOfType [System.Collections.Hashtable] + } + + It 'Returns the same values passed as parameters' { + $getTargetResourceResult = Get-TargetResource @mockDefaultParameters + $getTargetResourceResult.Name | Should Be $mockDefaultParameters.Name + $getTargetResourceResult.RetryIntervalSec | Should Be $mockDefaultParameters.RetryIntervalSec + $getTargetResourceResult.RetryCount | Should Be $mockDefaultParameters.RetryCount + } + + Assert-VerifiableMocks + } + } + + Describe 'xCluster\Set-TargetResource' -Tag Set { + Context 'When computers domain name cannot be evaluated' { + $mockDynamicDomainName = $null + + It 'Should throw the correct error message' { + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockCimInstance_ParameterFilter -Verifiable + + $mockCorrectErrorRecord = Get-InvalidOperationRecord -Message ($script:localizedData.ClusterAbsentAfterTimeOut -f $mockClusterName, ($mockRetryCount-1), $mockRetryIntervalSec) + { Set-TargetResource @mockDefaultParameters } | Should Throw $mockCorrectErrorRecord + } + } + + Context 'When the system is not in the desired state' { + BeforeEach { + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockCimInstance_ParameterFilter -Verifiable + } + + Context 'When the cluster does not exist' { + Context 'When Get-Cluster throws an error' { + BeforeEach { + # This is used for the evaluation of a cluster that does not exist + Mock -CommandName Get-Cluster -MockWith { + throw 'Mock Get-Cluster throw error' + } -ParameterFilter $mockGetCluster_ParameterFilter + } + + $mockDynamicDomainName = $mockDomainName + + It 'Should throw the correct error message' { + $mockCorrectErrorRecord = Get-InvalidOperationRecord -Message ($script:localizedData.ClusterAbsentAfterTimeOut -f $mockClusterName, $mockRetryCount, $mockRetryIntervalSec) + { Set-TargetResource @mockDefaultParameters } | Should Throw $mockCorrectErrorRecord + } + + Assert-VerifiableMocks + } + } + } + + Context 'When the system is in the desired state' { + Context 'When the cluster exist' { + BeforeEach { + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockCimInstance_ParameterFilter -Verifiable + Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable + } + + $mockDynamicDomainName = $mockDomainName + + It 'Should not throw any error' { + { Set-TargetResource @mockDefaultParameters } | Should Not Throw + } + + Assert-VerifiableMocks + } + } + } + + Describe 'xCluster\Test-TargetResource' -Tag Test { + BeforeEach { + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance -ParameterFilter $mockCimInstance_ParameterFilter -Verifiable + } + + Context 'When computers domain name cannot be evaluated' { + $mockDynamicDomainName = $null + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockDefaultParameters + $testTargetResourceResult | Should Be $false + } + } + + Context 'When the system is not in the desired state' { + Context 'When the cluster does not exist' { + Context 'When Get-Cluster throws an error' { + BeforeEach { + # This is used for the evaluation of a cluster that does not exist. + Mock -CommandName Get-Cluster -MockWith { + throw 'Mock Get-Cluster throw error' + } -ParameterFilter $mockGetCluster_ParameterFilter + } + + $mockDynamicDomainName = $mockDomainName + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockDefaultParameters + $testTargetResourceResult | Should Be $false + } + + Assert-VerifiableMocks + } + + Context 'When Get-Cluster returns nothing' { + BeforeEach { + # This is used for the evaluation of a cluster that does not exist. + Mock -CommandName Get-Cluster -MockWith { + $null + } -ParameterFilter $mockGetCluster_ParameterFilter + } + + $mockDynamicDomainName = $mockDomainName + + It 'Should return the value $false' { + $testTargetResourceResult = Test-TargetResource @mockDefaultParameters + $testTargetResourceResult | Should Be $false + } + + Assert-VerifiableMocks + } + } + } + + Context 'When the system is in the desired state' { + Context 'When the cluster exist' { + BeforeEach { + Mock -CommandName Get-Cluster -MockWith $mockGetCluster -ParameterFilter $mockGetCluster_ParameterFilter -Verifiable + } + + $mockDynamicDomainName = $mockDomainName + + It 'Should return the value $true' { + $testTargetResourceResult = Test-TargetResource @mockDefaultParameters + $testTargetResourceResult | Should Be $true + } + + Assert-VerifiableMocks + } + } + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/Tests/Unit/Stubs/FailoverClusters.stubs.psm1 b/Tests/Unit/Stubs/FailoverClusters.stubs.psm1 index 3805433..002c515 100644 --- a/Tests/Unit/Stubs/FailoverClusters.stubs.psm1 +++ b/Tests/Unit/Stubs/FailoverClusters.stubs.psm1 @@ -1,4 +1,4 @@ -<# +<# .SYNOPSIS This is stub cmdlets for the module FailoverClusters which can be used in Pester unit tests to be able to test code without having the actual module installed. @@ -16,560 +16,2266 @@ param() function Add-ClusterCheckpoint { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216179')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${ResourceName}, [Parameter(HelpMessage='Name of crypto checkpoint to add.')] [string] ${CryptoCheckpointName}, [Parameter(HelpMessage='Type of crypto checkpoint to add.')] [string] ${CryptoCheckpointType}, [Parameter(HelpMessage='Key of crypto checkpoint to add.')] [string] ${CryptoCheckpointKey}, [Parameter(HelpMessage='Name of registry checkpoint to add.')] [string] ${RegistryCheckpoint}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216179')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${ResourceName}, + + [Parameter(HelpMessage='Name of crypto checkpoint to add.')] + [string] + ${CryptoCheckpointName}, + + [Parameter(HelpMessage='Type of crypto checkpoint to add.')] + [string] + ${CryptoCheckpointType}, + + [Parameter(HelpMessage='Key of crypto checkpoint to add.')] + [string] + ${CryptoCheckpointKey}, + + [Parameter(HelpMessage='Name of registry checkpoint to add.')] + [string] + ${RegistryCheckpoint}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterDisk { - [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216180')] param( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] [ValidateNotNull()] [psobject[]] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216180')] + param( + [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject[]] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterFileServerRole { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216183')] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Storage}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${StaticAddress}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${IgnoreNetwork}, [Parameter(Position=0)] [string] ${Name}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216183')] + param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Storage}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${StaticAddress}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${IgnoreNetwork}, + + [Parameter(Position=0)] + [string] + ${Name}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterGenericApplicationRole { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216184')] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] ${CommandLine}, [string] ${Parameters}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${CheckpointKey}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Storage}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${StaticAddress}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${IgnoreNetwork}, [Parameter(Position=0)] [string] ${Name}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216184')] + param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + ${CommandLine}, + + [string] + ${Parameters}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${CheckpointKey}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Storage}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${StaticAddress}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${IgnoreNetwork}, + + [Parameter(Position=0)] + [string] + ${Name}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterGenericScriptRole { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216186')] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] ${ScriptFilePath}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Storage}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${StaticAddress}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${IgnoreNetwork}, [Parameter(Position=0)] [string] ${Name}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216186')] + param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + ${ScriptFilePath}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Storage}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${StaticAddress}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${IgnoreNetwork}, + + [Parameter(Position=0)] + [string] + ${Name}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterGenericServiceRole { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216187')] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] ${ServiceName}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${CheckpointKey}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Storage}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${StaticAddress}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${IgnoreNetwork}, [Parameter(Position=0)] [string] ${Name}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216187')] + param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + ${ServiceName}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${CheckpointKey}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Storage}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${StaticAddress}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${IgnoreNetwork}, + + [Parameter(Position=0)] + [string] + ${Name}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterGroup { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216189')] param( [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(Position=1)] [Object] ${GroupType}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216189')] + param( + [Parameter(Mandatory=$true, Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(Position=1)] + [Object] + ${GroupType}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusteriSCSITargetServerRole { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=229636')] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Storage}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${StaticAddress}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${IgnoreNetwork}, [Parameter(Position=0)] [string] ${Name}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=229636')] + param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Storage}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${StaticAddress}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${IgnoreNetwork}, + + [Parameter(Position=0)] + [string] + ${Name}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterNode { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216190')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [switch] ${NoStorage}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216190')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [switch] + ${NoStorage}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterResource { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216192')] param( [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [Parameter(Position=1)] [ValidateNotNullOrEmpty()] [string] ${Group}, [Parameter(Mandatory=$true, Position=2)] [Alias('ResType','Type')] [ValidateNotNullOrEmpty()] [string] ${ResourceType}, [switch] ${SeparateMonitor}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216192')] + param( + [Parameter(Mandatory=$true, Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [Parameter(Position=1)] + [ValidateNotNullOrEmpty()] + [string] + ${Group}, + + [Parameter(Mandatory=$true, Position=2)] + [Alias('ResType','Type')] + [ValidateNotNullOrEmpty()] + [string] + ${ResourceType}, + + [switch] + ${SeparateMonitor}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterResourceDependency { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216193')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Resource}, [Parameter(Position=1)] [ValidateNotNullOrEmpty()] [string] ${Provider}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216193')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Resource}, + + [Parameter(Position=1)] + [ValidateNotNullOrEmpty()] + [string] + ${Provider}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterResourceType { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216194')] param( [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [Parameter(Mandatory=$true, Position=1)] [ValidateNotNullOrEmpty()] [string] ${Dll}, [Parameter(Position=2)] [ValidateNotNullOrEmpty()] [string] ${DisplayName}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216194')] + param( + [Parameter(Mandatory=$true, Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [Parameter(Mandatory=$true, Position=1)] + [ValidateNotNullOrEmpty()] + [string] + ${Dll}, + + [Parameter(Position=2)] + [ValidateNotNullOrEmpty()] + [string] + ${DisplayName}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterScaleOutFileServerRole { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216200')] param( [Parameter(Position=0)] [string] ${Name}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216200')] + param( + [Parameter(Position=0)] + [string] + ${Name}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterServerRole { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216195')] param( [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Storage}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${StaticAddress}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${IgnoreNetwork}, [Parameter(Position=0)] [string] ${Name}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216195')] + param( + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Storage}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${StaticAddress}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${IgnoreNetwork}, + + [Parameter(Position=0)] + [string] + ${Name}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterSharedVolume { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216196')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216196')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterVirtualMachineRole { - [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216198')] param( [ValidateNotNullOrEmpty()] [string] ${Name}, [Parameter(Position=0, ValueFromPipelineByPropertyName=$true)] [string] ${VMName}, [Alias('VM')] [string] ${VirtualMachine}, [Parameter(ValueFromPipelineByPropertyName=$true)] [guid] ${VMId}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216198')] + param( + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [Parameter(Position=0, ValueFromPipelineByPropertyName=$true)] + [string] + ${VMName}, + + [Alias('VM')] + [string] + ${VirtualMachine}, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [guid] + ${VMId}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Add-ClusterVMMonitoredItem { - [CmdletBinding(DefaultParameterSetName='VirtualMachine', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216199')] param( [System.Collections.Specialized.StringCollection] ${Service}, [string] ${EventLog}, [string] ${EventSource}, [int] ${EventId}, [switch] ${OverrideServiceRecoveryActions}, [Parameter(ParameterSetName='VirtualMachine', Position=0)] [Alias('VM')] [ValidateNotNullOrEmpty()] [string] ${VirtualMachine}, [Parameter(ParameterSetName='VMId', ValueFromPipelineByPropertyName=$true)] [guid] ${VMId}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='VirtualMachine', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216199')] + param( + [System.Collections.Specialized.StringCollection] + ${Service}, + + [string] + ${EventLog}, + + [string] + ${EventSource}, + + [int] + ${EventId}, + + [switch] + ${OverrideServiceRecoveryActions}, + + [Parameter(ParameterSetName='VirtualMachine', Position=0)] + [Alias('VM')] + [ValidateNotNullOrEmpty()] + [string] + ${VirtualMachine}, + + [Parameter(ParameterSetName='VMId', ValueFromPipelineByPropertyName=$true)] + [guid] + ${VMId}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Block-ClusterAccess { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216202')] param( [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${User}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216202')] + param( + [Parameter(Mandatory=$true, Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${User}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Clear-ClusterDiskReservation { - [CmdletBinding(ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216203')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Node}, [Parameter(Mandatory=$true)] [uint32[]] ${Disk}, [switch] ${Force} + [CmdletBinding(ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216203')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Node}, + + [Parameter(Mandatory=$true)] + [uint32[]] + ${Disk}, + + [switch] + ${Force} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Clear-ClusterNode { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216205')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [switch] ${Force}, [int] ${Wait}, [switch] ${CleanupDisks}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216205')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [switch] + ${Force}, + + [int] + ${Wait}, + + [switch] + ${CleanupDisks}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-Cluster { - [CmdletBinding(DefaultParameterSetName='Name', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216206')] param( [Parameter(ParameterSetName='Name', Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [string] ${Domain} + [CmdletBinding(DefaultParameterSetName='Name', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216206')] + param( + [Parameter(ParameterSetName='Name', Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [string] + ${Domain} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterAccess { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216207')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${User}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216207')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${User}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterAvailableDisk { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216208')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Cluster}, [Parameter(ValueFromPipeline=$true)] [ValidateNotNull()] [ciminstance] ${Disk}, [switch] ${All}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216208')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Cluster}, + + [Parameter(ValueFromPipeline=$true)] + [ValidateNotNull()] + [ciminstance] + ${Disk}, + + [switch] + ${All}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterCheckpoint { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216209')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${ResourceName}, [Parameter(HelpMessage='Searches for checkpoints with a specific name, wildcard expressions are accepted.')] [string] ${CheckpointName}, [Parameter(HelpMessage='If specified, command will output registry checkpoints.')] [switch] ${RegistryCheckpoint}, [Parameter(HelpMessage='If specified, command will output crypto checkpoints.')] [switch] ${CryptoCheckpoint}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216209')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${ResourceName}, + + [Parameter(HelpMessage='Searches for checkpoints with a specific name, wildcard expressions are accepted.')] + [string] + ${CheckpointName}, + + [Parameter(HelpMessage='If specified, command will output registry checkpoints.')] + [switch] + ${RegistryCheckpoint}, + + [Parameter(HelpMessage='If specified, command will output crypto checkpoints.')] + [switch] + ${CryptoCheckpoint}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterGroup { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216210')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ValueFromPipelineByPropertyName=$true)] [guid] ${VMId}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216210')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [guid] + ${VMId}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterLog { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216212')] param( [Parameter(Position=0)] [ValidateNotNull()] [System.Collections.Specialized.StringCollection] ${Node}, [ValidateNotNullOrEmpty()] [string] ${Destination}, [Alias('Span')] [uint32] ${TimeSpan}, [Parameter(HelpMessage='Generate the cluster log using local time instead of GMT.')] [Alias('lt')] [switch] ${UseLocalTime}, [Parameter(HelpMessage='Generate the cluster log without retrieving cluster state information.')] [Alias('scs')] [switch] ${SkipClusterState}, [Parameter(HelpMessage='Generate the cluster health logs.')] [switch] ${Health}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216212')] + param( + [Parameter(Position=0)] + [ValidateNotNull()] + [System.Collections.Specialized.StringCollection] + ${Node}, + + [ValidateNotNullOrEmpty()] + [string] + ${Destination}, + + [Alias('Span')] + [uint32] + ${TimeSpan}, + + [Parameter(HelpMessage='Generate the cluster log using local time instead of GMT.')] + [Alias('lt')] + [switch] + ${UseLocalTime}, + + [Parameter(HelpMessage='Generate the cluster log without retrieving cluster state information.')] + [Alias('scs')] + [switch] + ${SkipClusterState}, + + [Parameter(HelpMessage='Generate the cluster health logs.')] + [switch] + ${Health}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterNetwork { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216213')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216213')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterNetworkInterface { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216214')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Node}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Network}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216214')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Node}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Network}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterNode { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216215')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216215')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterOwnerNode { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216216')] param( [Alias('Res')] [ValidateNotNullOrEmpty()] [string] ${Resource}, [ValidateNotNullOrEmpty()] [string] ${Group}, [Alias('ResType')] [ValidateNotNullOrEmpty()] [string] ${ResourceType}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216216')] + param( + [Alias('Res')] + [ValidateNotNullOrEmpty()] + [string] + ${Resource}, + + [ValidateNotNullOrEmpty()] + [string] + ${Group}, + + [Alias('ResType')] + [ValidateNotNullOrEmpty()] + [string] + ${ResourceType}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterParameter { - [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216217')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216217')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterQuorum { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216218')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Cluster}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216218')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Cluster}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterResource { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216219')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ValueFromPipelineByPropertyName=$true)] [guid] ${VMId}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216219')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [guid] + ${VMId}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterResourceDependency { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216220')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Resource}, [switch] ${Guid}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216220')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Resource}, + + [switch] + ${Guid}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterResourceDependencyReport { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216221')] param( [ValidateNotNullOrEmpty()] [string] ${Resource}, [ValidateNotNullOrEmpty()] [string] ${Group}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216221')] + param( + [ValidateNotNullOrEmpty()] + [string] + ${Resource}, + + [ValidateNotNullOrEmpty()] + [string] + ${Group}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterResourceType { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216222')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216222')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterSharedVolume { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216223')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216223')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterSharedVolumeState { - [CmdletBinding()] param( [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Node}, [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding()] + param( + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Node}, + + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Get-ClusterVMMonitoredItem { - [CmdletBinding(DefaultParameterSetName='VirtualMachine', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216224')] param( [Parameter(ParameterSetName='VirtualMachine', Position=0)] [Alias('VM')] [ValidateNotNullOrEmpty()] [string] ${VirtualMachine}, [Parameter(ParameterSetName='VMId', ValueFromPipelineByPropertyName=$true)] [guid] ${VMId}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='VirtualMachine', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216224')] + param( + [Parameter(ParameterSetName='VirtualMachine', Position=0)] + [Alias('VM')] + [ValidateNotNullOrEmpty()] + [string] + ${VirtualMachine}, + + [Parameter(ParameterSetName='VMId', ValueFromPipelineByPropertyName=$true)] + [guid] + ${VMId}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Grant-ClusterAccess { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216225')] param( [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${User}, [switch] ${Full}, [switch] ${ReadOnly}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216225')] + param( + [Parameter(Mandatory=$true, Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${User}, + + [switch] + ${Full}, + + [switch] + ${ReadOnly}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Move-ClusterGroup { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216226')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [Parameter(Position=1)] [ValidateNotNullOrEmpty()] [string] ${Node}, [switch] ${IgnoreLocked}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216226')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [Parameter(Position=1)] + [ValidateNotNullOrEmpty()] + [string] + ${Node}, + + [switch] + ${IgnoreLocked}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Move-ClusterResource { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216227')] param( [Parameter(Position=0)] [ValidateNotNull()] [string] ${Name}, [Parameter(Position=1)] [ValidateNotNull()] [string] ${Group}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216227')] + param( + [Parameter(Position=0)] + [ValidateNotNull()] + [string] + ${Name}, + + [Parameter(Position=1)] + [ValidateNotNull()] + [string] + ${Group}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Move-ClusterSharedVolume { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216228')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [Parameter(Position=1)] [ValidateNotNullOrEmpty()] [string] ${Node}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216228')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [Parameter(Position=1)] + [ValidateNotNullOrEmpty()] + [string] + ${Node}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Move-ClusterVirtualMachineRole { - [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216229')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [Parameter(Position=1)] [ValidateNotNullOrEmpty()] [string] ${Node}, [switch] ${Cancel}, [Object] ${MigrationType}, [switch] ${IgnoreLocked}, [Parameter(ValueFromPipelineByPropertyName=$true)] [guid] ${VMId}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216229')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [Parameter(Position=1)] + [ValidateNotNullOrEmpty()] + [string] + ${Node}, + + [switch] + ${Cancel}, + + [Object] + ${MigrationType}, + + [switch] + ${IgnoreLocked}, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [guid] + ${VMId}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function New-Cluster { - [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216230')] param( [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Node}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${StaticAddress}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${IgnoreNetwork}, [switch] ${NoStorage}, [switch] ${S2D}, [Alias('aap')] [Object] ${AdministrativeAccessPoint}, [switch] ${Force} + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216230')] + param( + [Parameter(Mandatory=$true, Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Node}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${StaticAddress}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${IgnoreNetwork}, + + [switch] + ${NoStorage}, + + [switch] + ${S2D}, + + [Alias('aap')] + [Object] + ${AdministrativeAccessPoint}, + + [switch] + ${Force} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function New-ClusterNameAccount { - [CmdletBinding(DefaultParameterSetName='InputObject')] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] ${Name}, [Parameter(ParameterSetName='Credentials', Mandatory=$true)] [Parameter(ParameterSetName='InputObject')] [pscredential] ${Credentials}, [Parameter(ParameterSetName='Credentials', Mandatory=$true)] [Parameter(ParameterSetName='InputObject')] [string] ${Domain}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject')] + param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [Parameter(ParameterSetName='Credentials', Mandatory=$true)] + [Parameter(ParameterSetName='InputObject')] + [pscredential] + ${Credentials}, + + [Parameter(ParameterSetName='Credentials', Mandatory=$true)] + [Parameter(ParameterSetName='InputObject')] + [string] + ${Domain}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Remove-Cluster { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216231')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Cluster}, [switch] ${CleanupAD}, [switch] ${Force}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216231')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Cluster}, + + [switch] + ${CleanupAD}, + + [switch] + ${Force}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Remove-ClusterAccess { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216232')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${User}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216232')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${User}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Remove-ClusterCheckpoint { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216233')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${ResourceName}, [switch] ${Force}, [Parameter(HelpMessage='Searches for checkpoints with a specific name, regular expressions are accepted.')] [string] ${CheckpointName}, [Parameter(HelpMessage='If specified, command will remove registry checkpoints.')] [switch] ${RegistryCheckpoint}, [Parameter(HelpMessage='If specified, command will remove crypto checkpoints.')] [switch] ${CryptoCheckpoint}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216233')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${ResourceName}, + + [switch] + ${Force}, + + [Parameter(HelpMessage='Searches for checkpoints with a specific name, regular expressions are accepted.')] + [string] + ${CheckpointName}, + + [Parameter(HelpMessage='If specified, command will remove registry checkpoints.')] + [switch] + ${RegistryCheckpoint}, + + [Parameter(HelpMessage='If specified, command will remove crypto checkpoints.')] + [switch] + ${CryptoCheckpoint}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Remove-ClusterGroup { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216234')] param( [Parameter(ValueFromPipelineByPropertyName=$true)] [guid] ${VMId}, [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [switch] ${Force}, [switch] ${RemoveResources}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216234')] + param( + [Parameter(ValueFromPipelineByPropertyName=$true)] + [guid] + ${VMId}, + + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [switch] + ${Force}, + + [switch] + ${RemoveResources}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Remove-ClusterNode { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216235')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [switch] ${Force}, [int] ${Wait}, [switch] ${IgnoreStorageConnectivityLoss}, [switch] ${CleanupDisks}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216235')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [switch] + ${Force}, + + [int] + ${Wait}, + + [switch] + ${IgnoreStorageConnectivityLoss}, + + [switch] + ${CleanupDisks}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Remove-ClusterResource { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216236')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [switch] ${Force}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216236')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [switch] + ${Force}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Remove-ClusterResourceDependency { - [CmdletBinding(ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216237')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Resource}, [Parameter(Position=1)] [ValidateNotNullOrEmpty()] [string] ${Provider}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216237')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Resource}, + + [Parameter(Position=1)] + [ValidateNotNullOrEmpty()] + [string] + ${Provider}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Remove-ClusterResourceType { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216238')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216238')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Remove-ClusterSharedVolume { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216239')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216239')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Remove-ClusterVMMonitoredItem { - [CmdletBinding(DefaultParameterSetName='VirtualMachine', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216240')] param( [Parameter(ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [System.Collections.Specialized.StringCollection] ${Service}, [string] ${EventLog}, [string] ${EventSource}, [int] ${EventId}, [Parameter(ParameterSetName='VirtualMachine', Position=0)] [Alias('VM')] [ValidateNotNullOrEmpty()] [string] ${VirtualMachine}, [Parameter(ParameterSetName='VMId', ValueFromPipelineByPropertyName=$true)] [guid] ${VMId}, [int] ${Wait}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='VirtualMachine', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216240')] + param( + [Parameter(ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [System.Collections.Specialized.StringCollection] + ${Service}, + + [string] + ${EventLog}, + + [string] + ${EventSource}, + + [int] + ${EventId}, + + [Parameter(ParameterSetName='VirtualMachine', Position=0)] + [Alias('VM')] + [ValidateNotNullOrEmpty()] + [string] + ${VirtualMachine}, + + [Parameter(ParameterSetName='VMId', ValueFromPipelineByPropertyName=$true)] + [guid] + ${VMId}, + + [int] + ${Wait}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Reset-ClusterVMMonitoredState { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216243')] param( [int] ${Wait} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216243')] + param( + [int] + ${Wait} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Resume-ClusterNode { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216244')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(Position=1)] [ValidateNotNullOrEmpty()] [Object] ${Failback}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216244')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(Position=1)] + [ValidateNotNullOrEmpty()] + [Object] + ${Failback}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Resume-ClusterResource { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216245')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [string] ${VolumeName}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216245')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [string] + ${VolumeName}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Set-ClusterLog { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216246')] param( [int] ${Size}, [int] ${Level}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216246')] + param( + [int] + ${Size}, + + [int] + ${Level}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Set-ClusterOwnerNode { - [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216247')] param( [ValidateNotNullOrEmpty()] [string] ${Resource}, [ValidateNotNullOrEmpty()] [string] ${Group}, [Parameter(Mandatory=$true, ValueFromRemainingArguments=$true)] [ValidateNotNull()] [System.Collections.Specialized.StringCollection] ${Owners}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216247')] + param( + [ValidateNotNullOrEmpty()] + [string] + ${Resource}, + + [ValidateNotNullOrEmpty()] + [string] + ${Group}, + + [Parameter(Mandatory=$true, ValueFromRemainingArguments=$true)] + [ValidateNotNull()] + [System.Collections.Specialized.StringCollection] + ${Owners}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Set-ClusterParameter { - [CmdletBinding(DefaultParameterSetName='NoMultiple', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216248')] param( [Parameter(ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [Parameter(ParameterSetName='Single Parameter', Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [Parameter(ParameterSetName='Multiple Parameter', Position=0)] [ValidateNotNull()] [hashtable] ${Multiple}, [Parameter(ParameterSetName='Single Parameter', Position=1)] [psobject] ${Value}, [switch] ${Create}, [switch] ${Delete}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='NoMultiple', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216248')] + param( + [Parameter(ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [Parameter(ParameterSetName='Single Parameter', Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [Parameter(ParameterSetName='Multiple Parameter', Position=0)] + [ValidateNotNull()] + [hashtable] + ${Multiple}, + + [Parameter(ParameterSetName='Single Parameter', Position=1)] + [psobject] + ${Value}, + + [switch] + ${Create}, + + [switch] + ${Delete}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Set-ClusterQuorum { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216249')] param( [ValidateNotNullOrEmpty()] [string] ${DiskOnly}, [Alias('NodeMajority')] [switch] ${NoWitness}, [Alias('NodeAndDiskMajority')] [ValidateNotNullOrEmpty()] [string] ${DiskWitness}, [Alias('NodeAndFileShareMajority')] [ValidateNotNullOrEmpty()] [string] ${FileShareWitness}, [switch] ${CloudWitness}, [ValidateNotNullOrEmpty()] [string] ${AccountName}, [string] ${Endpoint}, [ValidateNotNullOrEmpty()] [string] ${AccessKey}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216249')] + param( + [ValidateNotNullOrEmpty()] + [string] + ${DiskOnly}, + + [Alias('NodeMajority')] + [switch] + ${NoWitness}, + + [Alias('NodeAndDiskMajority')] + [ValidateNotNullOrEmpty()] + [string] + ${DiskWitness}, + + [Alias('NodeAndFileShareMajority')] + [ValidateNotNullOrEmpty()] + [string] + ${FileShareWitness}, + + [switch] + ${CloudWitness}, + + [ValidateNotNullOrEmpty()] + [string] + ${AccountName}, + + [string] + ${Endpoint}, + + [ValidateNotNullOrEmpty()] + [string] + ${AccessKey}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Set-ClusterResourceDependency { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216250')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Resource}, [Parameter(Position=1)] [string] ${Dependency}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216250')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Resource}, + + [Parameter(Position=1)] + [string] + ${Dependency}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Start-Cluster { - [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216251')] param( [Parameter(Position=0)] [Alias('Cluster')] [ValidateNotNullOrEmpty()] [string] ${Name}, [Alias('ips')] [switch] ${IgnorePersistentState}, [int] ${Wait} + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216251')] + param( + [Parameter(Position=0)] + [Alias('Cluster')] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [Alias('ips')] + [switch] + ${IgnorePersistentState}, + + [int] + ${Wait} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Start-ClusterGroup { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216252')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [switch] ${IgnoreLocked}, [switch] ${ChooseBestNode}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216252')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [switch] + ${IgnoreLocked}, + + [switch] + ${ChooseBestNode}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Start-ClusterNode { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216253')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(HelpMessage='Specifies if the cluster is in a force quorum state.')] [Alias('fq','FixQuorum')] [switch] ${ForceQuorum}, [Parameter(HelpMessage='Specifies whether to clear quarantine state when starting the cluster node')] [Alias('cq')] [switch] ${ClearQuarantine}, [Parameter(HelpMessage='Specifies whether the cluster will bring online groups that were online when the cluster was shut down.')] [Alias('ips')] [switch] ${IgnorePersistentState}, [Alias('pq')] [switch] ${PreventQuorum}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216253')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(HelpMessage='Specifies if the cluster is in a force quorum state.')] + [Alias('fq','FixQuorum')] + [switch] + ${ForceQuorum}, + + [Parameter(HelpMessage='Specifies whether to clear quarantine state when starting the cluster node')] + [Alias('cq')] + [switch] + ${ClearQuarantine}, + + [Parameter(HelpMessage='Specifies whether the cluster will bring online groups that were online when the cluster was shut down.')] + [Alias('ips')] + [switch] + ${IgnorePersistentState}, + + [Alias('pq')] + [switch] + ${PreventQuorum}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Start-ClusterResource { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216254')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [switch] ${IgnoreLocked}, [switch] ${ChooseBestNode}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216254')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [switch] + ${IgnoreLocked}, + + [switch] + ${ChooseBestNode}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Stop-Cluster { - [CmdletBinding(DefaultParameterSetName='Cluster name', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216255')] param( [Parameter(ParameterSetName='Cluster name', Position=0)] [Alias('Name')] [ValidateNotNullOrEmpty()] [string] ${Cluster}, [switch] ${Force}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject} + [CmdletBinding(DefaultParameterSetName='Cluster name', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216255')] + param( + [Parameter(ParameterSetName='Cluster name', Position=0)] + [Alias('Name')] + [ValidateNotNullOrEmpty()] + [string] + ${Cluster}, + + [switch] + ${Force}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Stop-ClusterGroup { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216256')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [switch] ${IgnoreLocked}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216256')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [switch] + ${IgnoreLocked}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Stop-ClusterNode { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216257')] param( [Parameter(Position=0)] [System.Collections.Specialized.StringCollection] ${Name}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216257')] + param( + [Parameter(Position=0)] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Stop-ClusterResource { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216258')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [switch] ${IgnoreLocked}, [int] ${Wait}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216258')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [switch] + ${IgnoreLocked}, + + [int] + ${Wait}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Suspend-ClusterNode { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216259')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [switch] ${Drain}, [switch] ${ForceDrain}, [switch] ${Wait}, [Parameter(Position=1)] [ValidateNotNullOrEmpty()] [string] ${TargetNode}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216259')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [switch] + ${Drain}, + + [switch] + ${ForceDrain}, + + [switch] + ${Wait}, + + [Parameter(Position=1)] + [ValidateNotNullOrEmpty()] + [string] + ${TargetNode}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Suspend-ClusterResource { - [CmdletBinding(ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216260')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [string] ${VolumeName}, [Alias('FileSystemRedirectedAccess')] [switch] ${RedirectedAccess}, [switch] ${Force}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216260')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [string] + ${VolumeName}, + + [Alias('FileSystemRedirectedAccess')] + [switch] + ${RedirectedAccess}, + + [switch] + ${Force}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Test-Cluster { - [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216261')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Node}, [ValidateNotNullOrEmpty()] [System.Object[]] ${Disk}, [ValidateNotNullOrEmpty()] [System.Object[]] ${Pool}, [ValidateNotNullOrEmpty()] [string] ${ReportName}, [switch] ${List}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Include}, [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Ignore}, [switch] ${Force}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', ConfirmImpact='Medium', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216261')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Node}, + + [ValidateNotNullOrEmpty()] + [System.Object[]] + ${Disk}, + + [ValidateNotNullOrEmpty()] + [System.Object[]] + ${Pool}, + + [ValidateNotNullOrEmpty()] + [string] + ${ReportName}, + + [switch] + ${List}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Include}, + + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Ignore}, + + [switch] + ${Force}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Test-ClusterResourceFailure { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216262')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216262')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Update-ClusterFunctionalLevel { - [CmdletBinding(DefaultParameterSetName='InputObject')] param( [switch] ${Force}, [switch] ${WhatIf}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject')] + param( + [switch] + ${Force}, + + [switch] + ${WhatIf}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Update-ClusterIPResource { - [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216264')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [switch] ${Renew}, [switch] ${Release}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216264')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [switch] + ${Renew}, + + [switch] + ${Release}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Update-ClusterNetworkNameResource { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216265')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [System.Collections.Specialized.StringCollection] ${Name}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216265')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } function Update-ClusterVirtualMachineConfiguration { - [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216266')] param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string] ${Name}, [Parameter(ValueFromPipelineByPropertyName=$true)] [guid] ${VMId}, [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] [ValidateNotNull()] [psobject] ${InputObject}, [ValidateNotNullOrEmpty()] [string] ${Cluster} + [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=216266')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [string] + ${Name}, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [guid] + ${VMId}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} ) throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand diff --git a/Tests/Unit/Stubs/Write-ModuleStubFile.ps1 b/Tests/Unit/Stubs/Write-ModuleStubFile.ps1 index 95dcdae..96a75cc 100644 --- a/Tests/Unit/Stubs/Write-ModuleStubFile.ps1 +++ b/Tests/Unit/Stubs/Write-ModuleStubFile.ps1 @@ -59,73 +59,73 @@ function Write-ModuleStubFile $cmdletToStub = Get-Command -Module $module -CommandType 'Cmdlet' $cmdletToStub | ForEach-Object -Begin { - $operatingSystemInformation = Get-CimInstance -class Win32_OperatingSystem + $operatingSystemInformation = Get-CimInstance -class Win32_OperatingSystem + + "<#" + " .SYNOPSIS" + " This is stub cmdlets for the module $($module.Name) which can be used in" + " Pester unit tests to be able to test code without having the actual module installed." + if ($Description) + { + "" + " .DESCRIPTION" + " $($Description -join "`r`n ")" + } + "" + " .NOTES" + " Generated from module $($module.Name) (version $($module.Version.ToString())), on" + " operating system $($operatingSystemInformation.Caption) $($operatingSystemInformation.OSArchitecture) ($($operatingSystemInformation.Version))" + "#>" + "" + "<#" + " Suppressing this rule because these functions are from an external module" + " and are only being used as stubs." + "#>" + "[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')]" + "param()" + "" + } -Process { + $signature = $null + $command = $_ + $endOfDefinition = $false + $metadata = New-Object -TypeName System.Management.Automation.CommandMetaData -ArgumentList $command + $definition = [System.Management.Automation.ProxyCommand]::Create($metadata) + foreach ($line in $definition -split "`n") + { + # Replaces any type the in the $ReplaceType variable with the Object type. + $ReplaceType | ForEach-Object -Process { + $line = $line -replace "\[$_\]", '[Object]' + } + + $line = $line -replace 'SupportsShouldProcess=\$true, ', '' - "<#" - " .SYNOPSIS" - " This is stub cmdlets for the module $($module.Name) which can be used in" - " Pester unit tests to be able to test code without having the actual module installed." - if ($Description) + if ( $line.Contains( '})' ) ) { - "" - " .DESCRIPTION" - " $($Description -join "`r`n ")" + $line = $line.Remove( $line.Length - 2 ) + $endOfDefinition = $true } - "" - " .NOTES" - " Generated from module $($module.Name) (version $($module.Version.ToString())), on" - " operating system $($operatingSystemInformation.Caption) $($operatingSystemInformation.OSArchitecture) ($($operatingSystemInformation.Version))" - "#>" - "" - "<#" - " Suppressing this rule because these functions are from an external module" - " and are only being used as stubs." - "#>" - "[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')]" - "param()" - "" - } -Process { - $signature = $null - $command = $_ - $endOfDefinition = $false - $metadata = New-Object -TypeName System.Management.Automation.CommandMetaData -ArgumentList $command - $definition = [System.Management.Automation.ProxyCommand]::Create($metadata) - foreach ($line in $definition -split "`n") + + if ( $line.Trim() -ne '' ) { - # Replaces any type the in the $ReplaceType variable with the Object type. - $ReplaceType | ForEach-Object -Process { - $line = $line -replace "\[$_\]", '[Object]' - } - - $line = $line -replace 'SupportsShouldProcess=\$true, ', '' - - if ( $line.Contains( '})' ) ) - { - $line = $line.Remove( $line.Length - 2 ) - $endOfDefinition = $true - } - - if ( $line.Trim() -ne '' ) - { - $signature += " $line" - } - else - { - $signature += $line - } - - if ( $endOfDefinition ) - { - $signature += "`n )" - break - } + $signature += " $line" + } + else + { + $signature += $line } - "function $($command.Name) {" - "$signature" - "" - " throw '{0}: StubNotImplemented' -f $`MyInvocation.MyCommand" - "}" - "" - } | Out-String | Out-File (Join-Path -Path $Path -ChildPath "$($ModuleName).stubs.psm1") -Encoding utf8 -Append + if ( $endOfDefinition ) + { + $signature += "`n )" + break + } + } + + "function $($command.Name) {" + "$signature" + "" + " throw '{0}: StubNotImplemented' -f $`MyInvocation.MyCommand" + "}" + "" + } | Out-String | Out-File (Join-Path -Path $Path -ChildPath "$($ModuleName).stubs.psm1") -Encoding utf8 -Append } diff --git a/xFailOverCluster.psd1 b/xFailOverCluster.psd1 index 144f996..73e91f6 100644 --- a/xFailOverCluster.psd1 +++ b/xFailOverCluster.psd1 @@ -1,6 +1,6 @@ @{ -ModuleVersion = '1.7.0.0' +ModuleVersion = '1.8.0.0' GUID = '026e7fd8-06dd-41bc-b373-59366ab18679' @@ -8,7 +8,7 @@ Author = 'Microsoft Corporation' CompanyName = 'Microsoft Corporation' -Copyright = '(c) 2014 Microsoft Corporation. All rights reserved.' +Copyright = '(c) 2017 Microsoft Corporation. All rights reserved.' Description = 'Module containing DSC resources used to configure Failover Clusters.' @@ -33,102 +33,75 @@ PrivateData = @{ # IconUri = '' # ReleaseNotes of this module - ReleaseNotes = '- Changes to xClusterPreferredOwner - - Script Analyzer warnings have been fixed (issue 50). This also failed the - tests for the resource. + ReleaseNotes = '- Changes to xFailOverCluster + - Added a common resource helper module with helper functions for localization. + - Added helper functions; Get-LocalizedData, New-InvalidResultException, + New-ObjectNotFoundException, New-InvalidOperationException and + New-InvalidArgumentException. + - Fixed lint error MD034 and fixed typos in README.md. + - Opt-in for module files common tests ([issue 119](https://github.com/PowerShell/xFailOverCluster/issues/119)). + - Removed Byte Order Mark (BOM) from the files; CommonResourceHelper.psm1 and FailoverClusters.stubs.psm1. + - Opt-in for script files common tests ([issue 121](https://github.com/PowerShell/xFailOverCluster/issues/121)). + - Removed Byte Order Mark (BOM) from the files; CommonResourceHelper.Tests.ps1, + MSFT\_xCluster.Tests.ps1, MSFT\_xClusterDisk.Tests.ps1, + MSFT\_xClusterPreferredOwner.Tests.ps1, MSFT_xWaitForCluster.Tests.ps1. + - Added common test helper functions to help test the throwing of localized error strings. + - Get-InvalidArgumentRecord + - Get-InvalidOperationRecord + - Get-ObjectNotFoundException + - Get-InvalidResultException. + - Updated year to 2017 in license file and module manifest ([issue 131](https://github.com/PowerShell/xFailOverCluster/issues/131)). - Changes to xClusterDisk - - Fixed test that was failing in AppVeyor (issue 55). -- Changes to xFailOverCluster - - Added "Code of Conduct" text to the README.md (issue 44). - - Added TOC for all resources in the README.md (issue 43). - - Fixed typos and lint errors in README.md. - - Fixed style issue in example in README.md. - - Removed "Unreleased" "tag" from the resources xClusterQuorum and - xClusterDisk (issue 36) - - Added new sections to each resource (Requirements, Parameters and Examples) - in the README.md. Some does not yet have any examples, so they are set to - "None.". - - Added GitHub templates PULL\_REQUEST\_TEMPLATE, ISSUE_TEMPLATE and - CONTRIBUTING.md (issue 45). - - Split the change log from README.md to a separate file CHANGELOG.md - (issue 48). - - Added the resource xClusterPreferredOwner to README.md (issue 51). - - Added the resource xClusterNetwork to README.md (issue 56). - - Removed Credential parameter from parameter list for xWaitForCluster. - Parameter Credential does not exist in the schema.mof of the resource - (issue 62). - - Now all parameters in the README.md list their data type and type qualifier - (issue 58.) - - Added Import-DscResource to example in README.md. - - Added CodeCov and opt-in for all common tests (issue 41). - - Added CodeCov badge to README.md - - Fixed CodeCov badge links so they now can be clicked on. - - Fixed lint rule MD013 in CHANGELOG.md. - - Fixed lint rule MD013 in README.md. - - Fixed lint rule MD024 in README.md. - - Fixed lint rule MD032 in README.md. - - Removed example from README.md (issue 42). - - Fixed typo in filename for ISSUE\_TEMPLATE. Was "ISSUE\_TEMPLATE", now it is - correctly "ISSUE\_TEMPLATE.md". - - Changed appveyor.yml to use the new default test framework in the AppVeyor - module in DscResource.Tests (AppVeyor.psm1). - - Added VS Code workspace settings file with formatting settings matching the - Style Guideline (issue 67). That will make it possible inside VS Code to - press SHIFT+ALT+F, or press F1 and choose "Format document" in the list. The - PowerShell code will then be formatted according to the Style Guideline - (although maybe not complete, but would help a long way). - - Added new stubs for FailoverClusters module - (Tests\Unit\Stubs\FailoverClusters.stubs.psm1) to be able to run unit tests - on a computer that does not have or can install Failover Clustering - PowerShell module. - - Added a script file (Tests\Unit\Stubs\Write-ModuleStubFile.ps1) to be able - to rebuild the stub file (FailoverClusters.stubs.psm1) whenever needed. - - Added code block around types in README.md. + - Enabled localization for all strings ([issue 84](https://github.com/PowerShell/xFailOverCluster/issues/84)). + - Fixed the OutputType data type that was not fully qualified. + - Minor style changes. + - Fixed Script Analyzer warnings for Write-Verbose. +- Changes to xClusterNetwork + - Replaced the URL for the parameter Role in README.md. The new URL is a more + generic description of the possible settings for the Role parameter. The + previous URL was still correct but focused on Hyper-V in particular. + - Fixed typos in parameter descriptions in README.md, comment-based help and schema.mof. + - Enabled localization for all strings ([issue 85](https://github.com/PowerShell/xFailOverCluster/issues/85)). + - Minor style changes. + - Fixed Script Analyzer warnings for Write-Verbose. - Changes to xCluster - - Added examples - - 1-CreateFirstNodeOfAFailoverCluster.ps1 - - 2-JoinAdditionalNodeToFailoverCluster.ps1 - - 3-CreateFailoverClusterWithTwoNodes.ps1 (this is the example from README.md) - - Fixed typo in xCluster parameter description. - - Added links to examples from README.md - - Refactored the unit test for this resource to use stubs and increase coverage - (issue 73). - - Removed the password file (MSFT_xCluster.password.txt) which seemed unnecessary. - - Test-TargetResource now throws and error if domain name cannot be evaluated - (issue 72). - - Set-TargetResource now correctly throws and error if domain name cannot be - evaluated (issue 71). + - Resolved Script Analyzer rule warnings by changing Get-WmiObject to + Get-CimInstance ([issue 49](https://github.com/PowerShell/xFailOverCluster/issues/49)). + - Minor style change in tests. Removed "-" in front of "-Be", "-Not", "-Throw", + etc. + - Enabled localization for all strings ([issue 83](https://github.com/PowerShell/xFailOverCluster/issues/83)). + - Added tests to improve code coverage. + - Fixed random problem with tests failing with error "Invalid token for + impersonation - it cannot be duplicated." ([issue 133](https://github.com/PowerShell/xFailOverCluster/issues/133)). + - Minor style changes. + - Fixed Script Analyzer warnings for Write-Verbose. - Changes to xWaitForCluster - - Added example - - 1-WaitForFailoverClusterToBePresent.ps1 - - Added link to example from README.md -- Changes to xClusterDisk - Refactored the unit test for this resource to use stubs and increase coverage - (issue 74). - - Removed an evaluation that called Test-TargetResource in Set-TargetResource - method and instead added logic so that Set-TargetResource evaluates if it - should remove a disk (issue 90). + ([issue 78](https://github.com/PowerShell/xFailOverCluster/issues/78)). + - Now the Test-TargetResource correctly returns false if the domain name cannot + be evaluated ([issue 107](https://github.com/PowerShell/xFailOverCluster/issues/107)). - Changed the code to be more aligned with the style guideline. - - Added examples (issue 46) - - 1-AddClusterDisk.ps1 - - 2-RemoveClusterDisk.ps1 - - Added links to examples from README.md. -- Changes to xClusterPreferredOwner + - Updated parameter description in the schema.mof. + - Resolved Script Analyzer warnings ([issue 54](https://github.com/PowerShell/xFailOverCluster/issues/54)). + - Enabled localization for all strings ([issue 88](https://github.com/PowerShell/xFailOverCluster/issues/88)). + - Minor style changes. +- Changes to xClusterQuorum - Refactored the unit test for this resource to use stubs and increase coverage - (issue 76). + ([issue 77](https://github.com/PowerShell/xFailOverCluster/issues/77)). - Changed the code to be more aligned with the style guideline. - - Added examples (issue 52) - - 1-AddPreferredOwner.ps1 - - 2-RemovePreferredOwner.ps1 - - Added links to examples from README.md. -- Changes to xClusterNetwork - - Refactored the unit test for this resource to use stubs and increase coverage - (issue 75). - - Changed the code to be more aligned with the style guideline. - - Updated resource and parameter description in README.md and schema.mof. - - Added example (issue 57) - - 1-ChangeClusterNetwork.ps1 + - Updated parameter description in the schema.mof. + - Added example ([issue 47](https://github.com/PowerShell/xFailOverCluster/issues/47)) + - 1-SetQuorumToNodeMajority.ps1 + - 2-SetQuorumToNodeAndDiskMajority.ps1 + - 3-SetQuorumToNodeAndFileShareMajority.ps1 + - 4-SetQuorumToDiskOnly.ps1 - Added links to examples from README.md. + - Minor style changes. + - Enabled localization for all strings ([issue 87](https://github.com/PowerShell/xFailOverCluster/issues/87)). +- Changes to xClusterPreferredOwner + - Enabled localization for all strings ([issue 86](https://github.com/PowerShell/xFailOverCluster/issues/86)). + - Fixed typo in the returned hash table from Get-TargetResource. + - Minor style changes. ' @@ -138,3 +111,4 @@ PrivateData = @{ } +