├── Tooling ├── DscOperations │ ├── DscOperations.psm1 │ ├── Invoke-DscApply.ps1 │ ├── Clear-DscTemporaryModule.ps1 │ ├── Invoke-DscPull.ps1 │ ├── Clear-DscEventLog.ps1 │ ├── DscOperations.psd1 │ └── Set-DscClient.ps1 ├── DscConfiguration │ ├── New-Credential.ps1 │ ├── Remove-PlainTextPassword.ps1 │ ├── New-ConfigurationDataStore.ps1 │ ├── Get-Hashtable.ps1 │ ├── Get-ApplicationConfigurationData.ps1 │ ├── Get-CredentialConfigurationData.ps1 │ ├── Add-NodeRoleFromServiceConfigurationData.Tests.ps1 │ ├── Get-ServiceConfigurationData.ps1 │ ├── Get-SiteDataConfigurationData.ps1 │ ├── Get-AllNodesConfigurationData.ps1 │ ├── ConvertTo-CredentialLookup.ps1 │ ├── Test-LocalCertificate.ps1 │ ├── Add-EncryptedPassword.ps1 │ ├── Add-NodeRoleFromServiceConfigurationData.ps1 │ ├── Get-EncryptedPassword.ps1 │ ├── DscConfiguration.psm1 │ ├── Get-ConfigurationData.ps1 │ ├── DscConfiguration.psd1 │ ├── New-DscNodeMetadata.ps1 │ ├── ConvertTo-EncryptedFile.ps1 │ └── ConvertFrom-EncryptedFile.ps1 ├── dscbuild │ ├── Test-DscResourceIsValid.ps1 │ ├── Invoke-DscResourceUnitTest.ps1 │ ├── Clear-CachedDscResource.ps1 │ ├── Clear-InstalledDscResource.ps1 │ ├── Compress-DscResourceModule.ps1 │ ├── New-DscChecksumFile.ps1 │ ├── Publish-DscToolModule.ps1 │ ├── Publish-DscConfiguration.ps1 │ ├── Publish-DscResourceModule.ps1 │ ├── Copy-CurrentDscResource.ps1 │ ├── DscResourceWmiClass.ps1 │ ├── New-DscZipFile.ps1 │ ├── Assert-DestinationDirectory.ps1 │ ├── Get-DscResourceVersion.ps1 │ ├── Where-DscResource.ps1 │ ├── DscBuild.psd1 │ ├── Invoke-DscConfiguration.ps1 │ ├── Where-DscResource.Tests.ps1 │ ├── DscBuild.psm1 │ ├── Invoke-DscBuild.ps1 │ ├── Update-ModuleMetadataVersion.ps1 │ ├── Resolve-ConfigurationProperty.Tests.ps1 │ └── Resolve-ConfigurationProperty.ps1 ├── cDscDiagnostics │ ├── cDscDiagnostics.psd1 │ ├── cDscDiagnosticsFormat.ps1xml │ └── cDscDiagnostics.psm1.Tests.ps1 ├── DscDevelopment │ ├── Test-MofFile.ps1 │ ├── DscDevelopment.psm1 │ ├── DscResourceTemplate.Tests.ps1 │ ├── DscResourceTemplate.psm1 │ ├── New-DscResourceFromModule.ps1 │ ├── DscDevelopment.psd1 │ ├── New-DscCompositeResource.ps1 │ ├── Test-DscBuild.ps1 │ ├── Deserializer.ps1 │ └── New-MofFile.ps1 └── cDscResourceDesigner │ └── cDscResourceDesigner.psd1 ├── .gitignore ├── .gitattributes ├── LICENSE.txt ├── README.md └── README.old.md /Tooling/DscOperations/DscOperations.psm1: -------------------------------------------------------------------------------- 1 | . $psscriptroot\Set-DscClient.ps1 2 | . $psscriptroot\Invoke-DscPull.ps1 3 | . $psscriptroot\Clear-DscEventLog.ps1 4 | . $psscriptroot\Clear-DscTemporaryModule.ps1 5 | . $psscriptroot\Invoke-DscApply.ps1 6 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/New-Credential.ps1: -------------------------------------------------------------------------------- 1 | function New-Credential 2 | { 3 | param ($username, $password) 4 | $securepassword = $password | ConvertTo-SecureString -AsPlainText -Force 5 | return (New-Object System.Management.Automation.PSCredential -ArgumentList $username, $securepassword) 6 | } 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Remove-PlainTextPassword.ps1: -------------------------------------------------------------------------------- 1 | function Remove-PlainTextPassword 2 | { 3 | param ( 4 | [parameter()] 5 | [string] 6 | $path 7 | ) 8 | 9 | Start-Sleep -seconds 2 10 | Write-Verbose "Removing plain text credentials from $path" 11 | Remove-Item $path -Confirm:$false -Force 12 | } 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .hg/** 2 | .hgignore 3 | Configuration/** 4 | **.sublime-workspace 5 | **.sublime-project 6 | Resources/StackExchangeConfigurations/ 7 | Resources/StackExchangeResources/DSCResources/StackExchange_IISKeys/ 8 | Resources/StackExchangeResources/DSCResources/StackExchange_MetaConfiguration/ 9 | Resources/StackExchangeResources/DSCResources/StackExchange_ScollectorInstaller/ 10 | Tooling/Pester/ -------------------------------------------------------------------------------- /Tooling/dscbuild/Test-DscResourceIsValid.ps1: -------------------------------------------------------------------------------- 1 | function Test-DscResourceIsValid { 2 | [cmdletbinding(SupportsShouldProcess=$true)] 3 | param () 4 | 5 | 6 | if ( Test-BuildResource ) { 7 | if ($pscmdlet.shouldprocess("modules from $($script:DscBuildParameters.ProgramFilesModuleDirectory)")) { 8 | dir $script:DscBuildParameters.ProgramFilesModuleDirectory | 9 | Where-DscResource -IsValid | 10 | Out-Null 11 | } 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Invoke-DscResourceUnitTest.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-DscResourceUnitTest { 2 | [cmdletbinding(SupportsShouldProcess=$true)] 3 | param () 4 | 5 | if ( Test-BuildResource ) { 6 | if ($pscmdlet.shouldprocess($script:DscBuildParameters.SourceResourceDirectory)) { 7 | Write-Verbose 'Running Resource Unit Tests.' 8 | Invoke-Pester -relative_path $script:DscBuildParameters.SourceResourceDirectory 9 | } 10 | } 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/New-ConfigurationDataStore.ps1: -------------------------------------------------------------------------------- 1 | function New-DscConfigurationDataStore { 2 | param ( 3 | [parameter(mandatory)] 4 | $Path 5 | ) 6 | $Path = (resolve-path $path).Path 7 | 8 | mkdir (join-path $Path 'AllNodes') | out-null 9 | mkdir (join-path $Path 'Credentials') | out-null 10 | mkdir (join-path $Path 'Services') | out-null 11 | mkdir (join-path $Path 'SiteData') | out-null 12 | mkdir (join-path $Path 'Applications') | out-null 13 | 14 | $script:ConfigurationDataPath = $Path 15 | } 16 | -------------------------------------------------------------------------------- /Tooling/cDscDiagnostics/cDscDiagnostics.psd1: -------------------------------------------------------------------------------- 1 | 2 | 3 | @{ 4 | 5 | 6 | ModuleVersion = '1.0.2' 7 | GUID = 'ef098cb4-f7e9-4763-b636-0cd9799e1c9a' 8 | 9 | Author = 'Microsoft Corporation' 10 | CompanyName = 'Microsoft Corporation' 11 | Copyright = '(c) 2013 Microsoft Corporation. All rights reserved.' 12 | 13 | Description = 'Module to help in reading details from DSC events' 14 | 15 | PowerShellVersion = '4.0' 16 | 17 | CLRVersion = '4.0' 18 | 19 | FunctionsToExport = @("*") 20 | 21 | NestedModules = @('cDscDiagnostics.psm1') 22 | } 23 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Clear-CachedDscResource.ps1: -------------------------------------------------------------------------------- 1 | function Clear-CachedDscResource { 2 | [cmdletbinding(SupportsShouldProcess=$true)] 3 | param() 4 | 5 | if ($pscmdlet.ShouldProcess($env:computername)) { 6 | Write-Verbose 'Stopping any existing WMI processes to clear cached resources.' 7 | Get-process -Name WmiPrvSE -erroraction silentlycontinue | stop-process -force 8 | 9 | Write-Verbose 'Clearing out any tmp WMI classes from tested resources.' 10 | Get-DscResourceWmiClass -class tmp* | remove-DscResourceWmiClass 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Get-Hashtable.ps1: -------------------------------------------------------------------------------- 1 | function Get-Hashtable 2 | { 3 | # Path to PSD1 file to evaluate 4 | param ( 5 | [parameter( 6 | Position = 0, 7 | ValueFromPipelineByPropertyName, 8 | Mandatory 9 | )] 10 | [ValidateNotNullOrEmpty()] 11 | [Alias('FullName')] 12 | [string] 13 | $Path 14 | ) 15 | process 16 | { 17 | Write-Verbose "Loading data from $Path." 18 | invoke-expression "DATA { $(get-content -raw -path $path) }" 19 | } 20 | } 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Get-ApplicationConfigurationData.ps1: -------------------------------------------------------------------------------- 1 | function Get-ApplicationConfigurationData { 2 | [cmdletbinding()] 3 | param () 4 | if (($script:ConfigurationData.Applications.Keys.Count -eq 0)) 5 | { 6 | Write-Verbose "Processing Applications from $($script:ConfigurationDataPath))." 7 | foreach ( $item in (dir (join-path $script:ConfigurationDataPath 'Applications\*.psd1')) ) 8 | { 9 | Write-Verbose "Loading data for site $($item.basename) from $($item.fullname)." 10 | $script:ConfigurationData.Applications += (Get-Hashtable $item.FullName) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tooling/DscDevelopment/Test-MofFile.ps1: -------------------------------------------------------------------------------- 1 | function Test-MofFile { 2 | param ( 3 | [parameter(Mandatory,Position=0)] 4 | $NodeId, 5 | [parameter(Position=1)] 6 | $ConfigurationPath = 'd:\temp\cimschema' 7 | ) 8 | Get-Process WmiPrvSE | Stop-Process -Force 9 | start-job -argumentlist $NodeId -scriptblock { 10 | param ($NodeId) 11 | ipmo dscdevelopment 12 | reset-cimclasscache 13 | dir 'C:\Program Files\WindowsPowerShell\Modules\' -Recurse -Include *.schema.mof | Add-CachedCimClass 14 | Import-CimInstances -MofInstanceFilePath (join-path $ConfigurationPath "$NodeId.mof") 15 | } | receive-job -wait 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Get-CredentialConfigurationData.ps1: -------------------------------------------------------------------------------- 1 | function Get-CredentialConfigurationData 2 | { 3 | [cmdletbinding()] 4 | param () 5 | if (($script:ConfigurationData.Credentials.Keys.Count -eq 0) ) 6 | { 7 | Write-Verbose "Processing Credentials from $($script:ConfigurationDataPath))." 8 | 9 | $script:ConfigurationData.Credentials = dir (join-path $script:ConfigurationDataPath 'Credentials\*.psd1.encrypted') | 10 | Get-DscEncryptedPassword -StoreName {$_.Name -replace '\.encrypted' -replace '\.psd1'} | 11 | ConvertTo-CredentialLookup 12 | 13 | } 14 | } 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Add-NodeRoleFromServiceConfigurationData.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.ps1", ".psm1") 3 | if (-not (Test-Path $sut)) 4 | { 5 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.ps1", ".ps1") 6 | } 7 | $pathtosut = join-path $here $sut 8 | if (-not (Test-Path $pathtosut)) 9 | { 10 | Write-Error "Failed to find script to test at $pathtosut" 11 | } 12 | 13 | 14 | iex ( gc $pathtosut -Raw ) 15 | 16 | describe 'how Add-NodeRoleFromServiceConfigurationData works' { 17 | context 'when ' {} 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Get-ServiceConfigurationData.ps1: -------------------------------------------------------------------------------- 1 | function Get-ServiceConfigurationData 2 | { 3 | [cmdletbinding()] 4 | param () 5 | if (($script:ConfigurationData.Services.Keys.Count -eq 0)) 6 | { 7 | Write-Verbose "Processing Services from $($script:ConfigurationDataPath))." 8 | foreach ( $item in (dir (join-path $script:ConfigurationDataPath 'Services\*.psd1')) ) 9 | { 10 | Write-Verbose "Loading data for site $($item.basename) from $($item.fullname)." 11 | $script:ConfigurationData.Services.Add($item.BaseName, (Get-Hashtable $item.FullName)) 12 | } 13 | } 14 | } 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Get-SiteDataConfigurationData.ps1: -------------------------------------------------------------------------------- 1 | function Get-SiteDataConfigurationData 2 | { 3 | [cmdletbinding()] 4 | param () 5 | if (($script:ConfigurationData.SiteData.Keys.Count -eq 0)) 6 | { 7 | Write-Verbose "Processing SiteData from $($script:ConfigurationDataPath))." 8 | foreach ( $item in (dir (join-path $script:ConfigurationDataPath 'SiteData\*.psd1')) ) 9 | { 10 | Write-Verbose "Loading data for site $($item.basename) from $($item.fullname)." 11 | $script:ConfigurationData.SiteData.Add($item.BaseName, (Get-Hashtable $item.FullName)) 12 | } 13 | } 14 | } 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Get-AllNodesConfigurationData.ps1: -------------------------------------------------------------------------------- 1 | function Get-AllNodesConfigurationData 2 | { 3 | [cmdletbinding()] 4 | param () 5 | if (($script:ConfigurationData.AllNodes.Count -eq 0)) 6 | { 7 | Write-Verbose "Processing AllNodes from $($script:ConfigurationDataPath)." 8 | $script:ConfigurationData.AllNodes = @() 9 | dir (join-path $script:ConfigurationDataPath 'AllNodes\*.psd1') | 10 | Get-Hashtable | 11 | foreach-object { 12 | Write-Verbose "Adding Name: $($_.Name)" 13 | $script:ConfigurationData.AllNodes += $_ 14 | } 15 | } 16 | } 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | 24 | *.ps1 -text diff merge 25 | *.psm1 -text diff merge 26 | *.psd1 -text diff merge 27 | *.mof -text diff merge 28 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Clear-InstalledDscResource.ps1: -------------------------------------------------------------------------------- 1 | function Clear-InstalledDscResource { 2 | [cmdletbinding(SupportsShouldProcess=$true)] 3 | param () 4 | 5 | if ( Test-BuildResource ) { 6 | if ($pscmdlet.shouldprocess("$($script:DscBuildParameters.ProgramFilesModuleDirectory)) based on modules in $($script:DscBuildParameters.SourceResourceDirectory)")) { 7 | Write-Verbose 'Clearing Program Files from old configuration modules.' 8 | dir $script:DscBuildParameters.SourceResourceDirectory | 9 | Foreach { dir (join-path $script:DscBuildParameters.ProgramFilesModuleDirectory $_.Name) -erroraction SilentlyContinue } | 10 | Remove-Item -Force -Recurse 11 | } 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/ConvertTo-CredentialLookup.ps1: -------------------------------------------------------------------------------- 1 | function ConvertTo-CredentialLookup 2 | { 3 | param ( 4 | [parameter( 5 | ValueFromPipeline, 6 | Mandatory 7 | )] 8 | [System.Collections.Hashtable] 9 | $PasswordHashtable 10 | ) 11 | begin 12 | { 13 | $CredentialHashtable = @{} 14 | } 15 | Process 16 | { 17 | foreach ($key in $PasswordHashtable.Keys) 18 | { 19 | Write-Verbose "Creating new credential for $key" 20 | $CredentialHashtable.Add($key, (New-Credential -username $key -password $PasswordHashtable[$key])) 21 | } 22 | } 23 | end 24 | { 25 | $CredentialHashtable 26 | } 27 | } 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Test-LocalCertificate.ps1: -------------------------------------------------------------------------------- 1 | function Test-LocalCertificate 2 | { 3 | param () 4 | if (-not [string]::IsNullOrEmpty($LocalCertificateThumbprint)) 5 | { 6 | Write-Verbose 'LocalCertificateThumbprint is present.' 7 | if (Test-Path $LocalCertificatePath) 8 | { 9 | Write-Verbose 'Certficate is present in the local certificate store.' 10 | return $true 11 | } 12 | else 13 | { 14 | Write-Warning 'Certficate specified is not in the certificate store.' 15 | return $false 16 | } 17 | } 18 | else 19 | { 20 | Write-Warning 'No local certificate supplied or configured with the DSC Local Configuration Manager.' 21 | return $false 22 | } 23 | } 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Tooling/DscDevelopment/DscDevelopment.psm1: -------------------------------------------------------------------------------- 1 | . $PSScriptRoot\New-DscResourceFromModule.ps1 2 | . $PSScriptRoot\New-MofFile.ps1 3 | . $PSScriptRoot\Test-DscBuild.ps1 4 | . $PSScriptRoot\Test-MofFile.ps1 5 | . $PSScriptRoot\New-DscCompositeResource.ps1 6 | . $PSScriptRoot\Deserializer.ps1 7 | 8 | 9 | 10 | function Get-Hashtable 11 | { 12 | # Path to PSD1 file to evaluate 13 | param ( 14 | [parameter( 15 | Position = 0, 16 | ValueFromPipelineByPropertyName, 17 | Mandatory 18 | )] 19 | [ValidateNotNullOrEmpty()] 20 | [Alias('FullName')] 21 | [string] 22 | $Path 23 | ) 24 | process 25 | { 26 | Write-Verbose "Loading data from $Path." 27 | invoke-expression "DATA { $(get-content -raw -path $path) }" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Compress-DscResourceModule.ps1: -------------------------------------------------------------------------------- 1 | function Compress-DscResourceModule { 2 | [cmdletbinding(SupportsShouldProcess=$true)] 3 | param () 4 | 5 | if ( Test-BuildResource ) { 6 | if ($pscmdlet.shouldprocess("from $($script:DscBuildParameters.ProgramFilesModuleDirectory) to $($script:DscBuildParameters.ModuleOutputPath)")) { 7 | Write-Verbose "Compressing tested modules: " 8 | foreach ($module in $script:DscBuildParameters.TestedModules) { 9 | Write-Verbose "`t$module" 10 | } 11 | 12 | Get-Item $script:DscBuildParameters.TestedModules | 13 | New-DscZipFile -ZipFile { join-path $script:DscBuildParameters.ModuleOutputPath "$($_.Name)" } -Force | 14 | Foreach-Object {Write-Verbose "New compressed resource module $($_.fullname)"} 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /Tooling/dscbuild/New-DscChecksumFile.ps1: -------------------------------------------------------------------------------- 1 | function New-DscChecksumFile 2 | { 3 | param ( 4 | [parameter(ValueFromPipeline)] 5 | [IO.FileSystemInfo] 6 | $InputObject 7 | ) 8 | process 9 | { 10 | $checksumfile = "$($inputobject.fullname).checksum" 11 | $hash = (Get-FileHash -path $inputobject.fullname).hash 12 | Write-Debug "Hash for $($InputObject.fullname) is $hash." 13 | if (test-path $checksumfile) 14 | { 15 | Write-verbose "Removing previous checksum file $checksumfile" 16 | remove-item $checksumfile -Force 17 | } 18 | [io.file]::AppendallText($checksumfile, $hash) 19 | Write-Debug "Hash written to file is $(Get-Content $checksumfile -Raw)." 20 | Write-Verbose "Wrote hash for $($InputObject.FullName) to $checksumfile" 21 | } 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Publish-DscToolModule.ps1: -------------------------------------------------------------------------------- 1 | function Publish-DscToolModule { 2 | [cmdletbinding(SupportsShouldProcess=$true)] 3 | param () 4 | 5 | if (Test-BuildTools) { 6 | if ($pscmdlet.shouldprocess($script:DscBuildParameters.SourceToolDirectory)) { 7 | dir $script:DscBuildParameters.SourceToolDirectory -exclude '.g*', '.hg' | 8 | Test-ModuleVersion -Destination $script:DscBuildParameters.DestinationToolsDirectory | 9 | copy-item -recurse -force -destination $script:DscBuildParameters.DestinationToolsDirectory 10 | } 11 | } 12 | <# 13 | if (Test-BuildResource) { 14 | if ($pscmdlet.shouldprocess($script:DscBuildParameters.TestedModules)) { 15 | Get-Item $script:DscBuildParameters.TestedModules | 16 | Test-ModuleVersion -Destination $script:DscBuildParameters.DestinationToolsDirectory | 17 | copy-item -recurse -force -destination $script:DscBuildParameters.DestinationToolsDirectory 18 | } 19 | } 20 | #> 21 | } 22 | 23 | -------------------------------------------------------------------------------- /Tooling/DscDevelopment/DscResourceTemplate.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.ps1", ".psm1") 3 | $pathtosut = join-path $here $sut 4 | 5 | iex (gc $pathtosut -Raw) 6 | 7 | Describe 'how Test-TargetResource responds' { 8 | Context 'when ' { 9 | $expected = '' 10 | $result = '' 11 | 12 | It "should call all the mocks" { 13 | Assert-VerifiableMocks 14 | } 15 | It 'should ' { 16 | $result | should be ($expected) 17 | } 18 | 19 | } 20 | } 21 | 22 | Describe 'how Set-TargetResource responds' { 23 | Context 'when ' { 24 | $expected = '' 25 | $result = '' 26 | 27 | It "should call all the mocks" { 28 | Assert-VerifiableMocks 29 | } 30 | It 'should ' { 31 | $result | should be ($expected) 32 | } 33 | 34 | } 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 PowerShell.Org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Tooling/dscbuild/Publish-DscConfiguration.ps1: -------------------------------------------------------------------------------- 1 | function Publish-DscConfiguration { 2 | [cmdletbinding(SupportsShouldProcess=$true)] 3 | param () 4 | 5 | if ( Test-BuildConfiguration ) { 6 | 7 | Write-Verbose 'Moving Processed Resource Modules from ' 8 | Write-Verbose "`t$($script:DscBuildParameters.ConfigurationOutputPath) to" 9 | Write-Verbose "`t$($script:DscBuildParameters.DestinationConfigurationDirectory)" 10 | if ($pscmdlet.shouldprocess("$($script:DscBuildParameters.ConfigurationOutputPath) to $($script:DscBuildParameters.DestinationConfigurationDirectory)")) { 11 | dir (join-path $script:DscBuildParameters.ConfigurationOutputPath '*.mof') | 12 | foreach-object { Write-Verbose "Moving $($_.name) to $($script:DscBuildParameters.DestinationConfigurationDirectory)"; $_ } | 13 | Move-Item -Destination $script:DscBuildParameters.DestinationConfigurationDirectory -force -PassThru | 14 | New-DscChecksumFile -Verbose:$false 15 | } 16 | } 17 | else { 18 | Write-Warning "Skipping publishing configurations." 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Tooling/DscOperations/Invoke-DscApply.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-DscApply 2 | { 3 | <# 4 | .Synopsis 5 | Uses the Configuration Agent to apply the configuration that is pending. 6 | .Description 7 | Uses the Configuration Agent to apply the configuration that is pending. 8 | If there is no configuration pending, this method reapplies the current configuration. 9 | .Example 10 | Invoke-DscApply -CimSession SERVER01 -Verbose 11 | .Example 12 | 1..7 | Invoke-DscApply -CimSession {"SERVER0$_"} 13 | 14 | #> 15 | param ( 16 | [parameter(ValueFromPipeline=$true, Position=0)] 17 | [alias('ComputerName', 'Name', '__Server')] 18 | $CimSession = $null 19 | ) 20 | 21 | Process { 22 | 23 | $parameters = @{ 24 | Namespace = 'root/microsoft/windows/desiredstateconfiguration' 25 | Class = 'MSFT_DscLocalConfigurationManager' 26 | MethodName = 'ApplyConfiguration' 27 | } 28 | 29 | Write-Verbose "" 30 | Write-Verbose "Forcing an application of configuration on $CimSession" 31 | Invoke-CimMethod @parameters @PSBoundParameters 32 | } 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Publish-DscResourceModule.ps1: -------------------------------------------------------------------------------- 1 | function Publish-DscResourceModule { 2 | [cmdletbinding(SupportsShouldProcess=$true)] 3 | param () 4 | 5 | if ( Test-BuildResource ) { 6 | Write-Verbose 'Moving Processed Resource Modules from ' 7 | Write-Verbose "`t$($script:DscBuildParameters.ModuleOutputPath) to" 8 | Write-Verbose "`t$($script:DscBuildParameters.DestinationModuleDirectory)" 9 | 10 | if ($pscmdlet.shouldprocess("$($script:DscBuildParameters.ModuleOutputPath) to $($script:DscBuildParameters.DestinationModuleDirectory)")) { 11 | 12 | Dir (join-path $script:DscBuildParameters.ModuleOutputPath '*.zip') | 13 | foreach-object { Write-Verbose "Checking if $($_.name) is already at $($script:DscBuildParameters.DestinationModuleDirectory)"; $_ } | 14 | Where-Object {-not (Test-Path (Join-Path $script:DscBuildParameters.DestinationModuleDirectory $_.name))} | 15 | foreach-object { Write-Verbose "Moving $($_.name) to $($script:DscBuildParameters.DestinationModuleDirectory)"; $_ } | 16 | Move-Item -Destination $script:DscBuildParameters.DestinationModuleDirectory -PassThru | 17 | New-DscChecksumFile -Verbose:$false 18 | } 19 | } 20 | } 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Tooling/cDscResourceDesigner/cDscResourceDesigner.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | # Version number of this module. 3 | ModuleVersion = '1.0.1' 4 | 5 | # ID used to uniquely identify this module 6 | GUID = '74951b31-1aa5-472b-9109-738de1bca38f' 7 | 8 | # Author of this module 9 | Author = 'Microsoft Corporation' 10 | 11 | # Company or vendor of this module 12 | CompanyName = 'Microsoft Corporation' 13 | 14 | # Copyright statement for this module 15 | Copyright = '(c) 2014 Microsoft Corporation. All rights reserved.' 16 | 17 | # Description of the functionality provided by this module 18 | Description = 'This module is meant to assist with the development and testing of DSC Resources.' 19 | 20 | # Minimum version of the Windows PowerShell engine required by this module 21 | PowerShellVersion = '4.0' 22 | 23 | # Minimum version of the common language runtime (CLR) required by this module 24 | CLRVersion = '4.0' 25 | 26 | # Script module or binary module file associated with this manifest. 27 | RootModule = 'cDscResourceDesigner.psm1' 28 | 29 | # Functions to export from this module 30 | FunctionsToExport = @('New-cDscResourceProperty', 31 | 'New-cDscResource', 32 | 'Update-cDscResource', 33 | 'Test-cDscResource', 34 | 'Test-cDscSchema', 35 | 'Import-cDscSchema') 36 | 37 | # Cmdlets to export from this module 38 | CmdletsToExport = '*' 39 | } 40 | -------------------------------------------------------------------------------- /Tooling/DscOperations/Clear-DscTemporaryModule.ps1: -------------------------------------------------------------------------------- 1 | function Clear-DscTemporaryModule { 2 | <# 3 | .Synopsis 4 | Deletes any directories in $env:programfiles\windowspowershell\modules\ whose names end in "_tmp". 5 | .Description 6 | Deletes any directories in $env:programfiles\windowspowershell\modules\ whose names end in "_tmp". 7 | This is due to a failure of the LCM in WMF4 to properly clean up modules with resources when a newer module is retrieved from a pull server. 8 | .Example 9 | Clear-DscTemporaryModule -ComputerName OR-WEB01 10 | #> 11 | param ( 12 | #Name of the computer(s) to target. 13 | [Parameter( 14 | ValueFromPipeline=$true, 15 | ValueFromPipelineByPropertyName=$true, 16 | Position='0' 17 | )] 18 | [ValidateNotNullOrEmpty()] 19 | [Alias('__Server', 'Name')] 20 | [string[]] 21 | $ComputerName = $env:COMPUTERNAME, 22 | 23 | #Alternate credentials to use in connecting to the remote computer(s). 24 | [Parameter( 25 | Position=2 26 | )] 27 | [System.Management.Automation.Credential()] 28 | $Credential = [System.Management.Automation.PSCredential]::Empty 29 | ) 30 | 31 | process { 32 | invoke-command @psboundparameters { 33 | dir $env:ProgramFiles\windowspowershell\modules\*_tmp | 34 | remove-item -recurse -force 35 | get-process WmiPrvSE | stop-process -force 36 | } 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Tooling/DscDevelopment/DscResourceTemplate.psm1: -------------------------------------------------------------------------------- 1 | # Fallback message strings in en-US 2 | DATA localizedData 3 | { 4 | # same as culture = "en-US" 5 | ConvertFrom-StringData @' 6 | '@ 7 | } 8 | 9 | if (Test-Path $PSScriptRoot\en-us) 10 | { 11 | $ModuleName = Split-Path $PSScriptRoot -Leaf 12 | Import-LocalizedData LocalizedData -filename "$($ModuleName)Provider.psd1" 13 | } 14 | 15 | function Get-TargetResource 16 | { 17 | [OutputType([Hashtable])] 18 | param ( 19 | [parameter(Mandatory = $true)] 20 | [ValidateNotNullOrEmpty()] 21 | [string] 22 | $Name, 23 | [ValidateSet('Present','Absent')] 24 | [string] 25 | $Ensure = 'Present' 26 | ) 27 | 28 | #Needs to return a hashtable that returns the current 29 | #status of the configuration component 30 | $Configuration = @{ 31 | Name = $Name 32 | } 33 | return $Configuration 34 | } 35 | 36 | function Set-TargetResource 37 | { 38 | param ( 39 | [parameter(Mandatory = $true)] 40 | [ValidateNotNullOrEmpty()] 41 | [string] 42 | $Name, 43 | [parameter()] 44 | [ValidateSet('Present','Absent')] 45 | [string] 46 | $Ensure = 'Present' 47 | ) 48 | 49 | 50 | } 51 | 52 | function Test-TargetResource 53 | { 54 | [OutputType([boolean])] 55 | param ( 56 | [parameter(Mandatory = $true)] 57 | [ValidateNotNullOrEmpty()] 58 | [string] 59 | $Name, 60 | [parameter()] 61 | [ValidateSet('Present','Absent')] 62 | [string] 63 | $Ensure = 'Present' 64 | ) 65 | 66 | #Needs to return a boolean 67 | return $true 68 | } 69 | 70 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Copy-CurrentDscResource.ps1: -------------------------------------------------------------------------------- 1 | function Copy-CurrentDscResource { 2 | [cmdletbinding(SupportsShouldProcess=$true)] 3 | param () 4 | 5 | Write-Verbose '' 6 | Write-Verbose "Pushing new configuration modules from $($script:DscBuildParameters.SourceResourceDirectory) to $($script:DscBuildParameters.ProgramFilesModuleDirectory)." 7 | 8 | if ($pscmdlet.shouldprocess("$($script:DscBuildParameters.SourceResourceDirectory) to $($script:DscBuildParameters.ProgramFilesModuleDirectory)")) { 9 | dir $script:DscBuildParameters.SourceResourceDirectory -exclude '.g*', '.hg' | 10 | Where-Object {$script:DscBuildParameters.ExcludedModules -notcontains $_.name} | 11 | Test-ModuleVersion -Destination $script:DscBuildParameters.ProgramFilesModuleDirectory | 12 | Copy-Item -Destination $script:DscBuildParameters.ProgramFilesModuleDirectory -Recurse -Force 13 | } 14 | 15 | 16 | } 17 | 18 | function Copy-CurrentDscTools { 19 | [cmdletbinding(SupportsShouldProcess=$true)] 20 | param () 21 | 22 | Write-Verbose '' 23 | Write-Verbose "Pushing new tools modules from $($script:DscBuildParameters.SourceToolDirectory) to $($script:DscBuildParameters.CurrentToolsDirectory)." 24 | 25 | if ($pscmdlet.shouldprocess("$($script:DscBuildParameters.SourceToolDirectory) to $($script:DscBuildParameters.CurrentToolsDirectory)")) { 26 | dir $script:DscBuildParameters.SourceToolDirectory -exclude '.g*', '.hg' | 27 | Test-ModuleVersion -Destination $script:DscBuildParameters.CurrentToolsDirectory | 28 | Copy-Item -Destination $script:DscBuildParameters.CurrentToolsDirectory -Recurse -Force 29 | } 30 | Write-Verbose '' 31 | } -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Add-EncryptedPassword.ps1: -------------------------------------------------------------------------------- 1 | function Add-DscEncryptedPassword 2 | { 3 | param ( 4 | [parameter(mandatory)] 5 | [string] 6 | $StoreName, 7 | [parameter()] 8 | [string] 9 | $Path = (Join-path $script:ConfigurationDataPath 'Credentials'), 10 | [parameter()] 11 | [string] 12 | $UserName, 13 | [parameter()] 14 | [string] 15 | $Password 16 | ) 17 | 18 | $EncryptedFilePath = Join-Path $Path "$StoreName.psd1.encrypted" 19 | $FilePath = Join-Path $Path "$StoreName.psd1" 20 | 21 | $Credentials = @{} 22 | if (Test-Path $EncryptedFilePath) 23 | { 24 | $Credentials += Get-DscEncryptedPassword -StoreName $StoreName -Path $Path 25 | Remove-Item $EncryptedFilePath 26 | foreach ($key in $Credentials.Keys) 27 | { 28 | Write-Verbose "Found credentials for $username." 29 | } 30 | } 31 | 32 | $Credentials.Add($UserName, $Password) 33 | Write-Verbose "Adding credentials for $Username. ($($credentials.keys.count) total.)" 34 | 35 | if (Test-Path $FilePath) 36 | { 37 | Remove-Item $FilePath -Confirm:$false 38 | } 39 | 40 | '@{' | Out-File $FilePath 41 | foreach ($key in $Credentials.Keys) 42 | { 43 | Write-Verbose "Persisting credentials for $key to disk." 44 | "'$key' = '$($Credentials[$key])'" | Out-File $FilePath -Append 45 | } 46 | '}' | Out-File $FilePath -Append 47 | 48 | Write-Verbose 'Encrypting credentials.' 49 | ConvertTo-EncryptedFile -Path $FilePath -CertificatePath $LocalCertificatePath 50 | Remove-PlainTextPassword $FilePath 51 | } 52 | 53 | Set-Alias -Name 'Add-EncryptedPassword' -value 'Add-DscEncryptedPassword' 54 | 55 | -------------------------------------------------------------------------------- /Tooling/DscDevelopment/New-DscResourceFromModule.ps1: -------------------------------------------------------------------------------- 1 | function New-DscResourceFromModule 2 | { 3 | param 4 | ( 5 | [ValidateScript({Test-Path $_ -PathType 'Container'})] 6 | [parameter(Position = 0, Mandatory = $true)] 7 | [string] $ModulePath, 8 | [string] $Author, 9 | [string] $CompanyName 10 | ) 11 | $ModuleName = Split-Path -Path $ModulePath -Leaf 12 | $psd1 = Join-Path -Path $ModulePath -ChildPath "$ModuleName.psd1" 13 | $psm1 = "$ModuleName.psm1" 14 | New-ModuleManifest -Path $psd1 -RootModule $psm1 -Author $Author -CompanyName $CompanyName -FunctionsToExport 'Get-TargetResource', 'Set-TargetResource', 'Test-TargetResource' 15 | New-MofFile -Path $ModulePath -Verbose 16 | } 17 | 18 | function New-DscResourceShell 19 | { 20 | param 21 | ( 22 | [ValidateScript({Test-Path $_ -PathType 'Container'})] 23 | [parameter(Position = 0, Mandatory = $true)] 24 | [string] $Path 25 | ) 26 | 27 | $ModuleName = Split-Path -Path $Path -Leaf 28 | $ModuleFile = Join-Path -Path $Path -ChildPath "$ModuleName.psm1" 29 | $ModuleFileTests = Join-Path -Path $Path -ChildPath "$ModuleName.Tests.ps1" 30 | 31 | if (-not (Test-Path $Path)) 32 | { 33 | mkdir $Path 34 | } 35 | if (-not (Test-Path $ModuleFile)) 36 | { 37 | Write-Verbose "Copying template for the module from $PSScriptRoot\DscResourceTemplate.psm1." 38 | Write-Verbose "`tto $ModuleFile" 39 | Copy-Item -Path "$PSScriptRoot\DscResourceTemplate.psm1" -Destination $ModuleFile -Force 40 | } 41 | if (-not (Test-Path $ModuleFileTests)) 42 | { 43 | Write-Verbose "Copying template for the module tests from $PSScriptRoot\DscResourceTemplate.Tests.ps1." 44 | Write-Verbose "`tto $ModuleFileTests" 45 | Copy-Item -Path "$PSScriptRoot\DscResourceTemplate.Tests.ps1" -Destination $ModuleFileTests -Force 46 | } 47 | } -------------------------------------------------------------------------------- /Tooling/dscbuild/DscResourceWmiClass.ps1: -------------------------------------------------------------------------------- 1 | function Get-DscResourceWmiClass { 2 | <# 3 | .Synopsis 4 | Retrieves WMI classes from the DSC namespace. 5 | .Description 6 | Retrieves WMI classes from the DSC namespace. 7 | .Example 8 | Get-DscResourceWmiClass -Class tmp* 9 | .Example 10 | Get-DscResourceWmiClass -Class 'MSFT_UserResource' 11 | #> 12 | param ( 13 | #The WMI Class name search for. Supports wildcards. 14 | [parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] 15 | [Alias('Name')] 16 | [string] 17 | $Class 18 | ) 19 | begin { 20 | $DscNamespace = "root/Microsoft/Windows/DesiredStateConfiguration" 21 | } 22 | process { 23 | Get-wmiobject -Namespace $DscNamespace -list @psboundparameters 24 | } 25 | } 26 | 27 | function Remove-DscResourceWmiClass { 28 | <# 29 | .Synopsis 30 | Removes a WMI class from the DSC namespace. 31 | .Description 32 | Removes a WMI class from the DSC namespace. 33 | .Example 34 | Get-DscResourceWmiClass -Class tmp* | Remove-DscResourceWmiClass 35 | .Example 36 | Remove-DscResourceWmiClass -Class 'tmpD460' 37 | 38 | #> 39 | param ( 40 | #The WMI Class name to remove. Supports wildcards. 41 | [parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] 42 | [alias('Name')] 43 | [string] 44 | $ResourceType 45 | ) 46 | begin { 47 | $DscNamespace = "root/Microsoft/Windows/DesiredStateConfiguration" 48 | } 49 | process { 50 | #Have to use WMI here because I can't find how to delete a WMI instance via the CIM cmdlets. 51 | (Get-wmiobject -Namespace $DscNamespace -list -Class $ResourceType).psbase.delete() 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Add-NodeRoleFromServiceConfigurationData.ps1: -------------------------------------------------------------------------------- 1 | function Add-NodeRoleFromServiceConfigurationData { 2 | [cmdletbinding()] 3 | param() 4 | 5 | Write-Verbose 'Adding Roles to service node.' 6 | $UpdatedNodes = $script:ConfigurationData.AllNodes 7 | foreach ($Service in $script:ConfigurationData.Services.Keys) { 8 | 9 | $UpdatedNodes = $UpdatedNodes | 10 | Add-ServiceConfigurationData -ServiceName $Service 11 | 12 | } 13 | $script:ConfigurationData.AllNodes = $UpdatedNodes 14 | } 15 | 16 | function Add-ServiceConfigurationData { 17 | [cmdletbinding()] 18 | param ( 19 | [string] 20 | $ServiceName, 21 | [parameter(ValueFromPipeline)] 22 | [System.Collections.Hashtable] 23 | $InputObject 24 | ) 25 | process { 26 | Write-Verbose "`tProcessing $($InputObject.Name) for Service $ServiceName" 27 | 28 | if ($script:ConfigurationData.Services[$ServiceName].Nodes -contains $InputObject.Name) { 29 | $InputObject = $InputObject | Assert-RolesConfigurationData -ServiceName $ServiceName 30 | } 31 | 32 | Write-Verbose "`t`tRoles on $($InputObject.Name) are: $($InputObject.Roles.Keys)" 33 | $InputObject 34 | } 35 | } 36 | 37 | 38 | 39 | function Assert-RolesConfigurationData { 40 | [cmdletbinding()] 41 | param ( 42 | [string] 43 | $ServiceName, 44 | [parameter(ValueFromPipeline)] 45 | [System.Collections.Hashtable] 46 | $InputObject 47 | ) 48 | 49 | process { 50 | if (-not ($InputObject.ContainsKey('Roles'))) { 51 | $InputObject.Roles = @{}` 52 | } 53 | foreach ($Role in $script:ConfigurationData.Services[$ServiceName].Roles) 54 | { 55 | if ($InputObject.Roles.ContainsKey($Role)) { 56 | $InputObject.Roles[$Role] += $ServiceName 57 | } 58 | else { 59 | $InputObject.Roles.Add($Role, [string[]]$ServiceName) 60 | } 61 | } 62 | $InputObject 63 | } 64 | } 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Get-EncryptedPassword.ps1: -------------------------------------------------------------------------------- 1 | function Get-DscEncryptedPassword 2 | { 3 | [cmdletbinding(DefaultParameterSetName='ByStoreName')] 4 | param ( 5 | [parameter( 6 | ParameterSetName = 'ByStoreName', 7 | ValueFromPipelineByPropertyName, 8 | Mandatory 9 | )] 10 | [Alias('BaseName')] 11 | [string] 12 | $StoreName, 13 | [parameter( 14 | ParameterSetName = 'ByStoreName' 15 | )] 16 | [string] 17 | $Path = (Join-path $script:ConfigurationDataPath 'Credentials'), 18 | [parameter( 19 | ParameterSetName = 'ByPipeline', 20 | ValueFromPipelineByPropertyName, 21 | Mandatory 22 | )] 23 | [Alias('FullName')] 24 | [string] 25 | $EncryptedFilePath, 26 | [parameter()] 27 | [string[]] 28 | $UserName 29 | ) 30 | process 31 | { 32 | if (Test-LocalCertificate) 33 | { 34 | if (-not $PSBoundParameters.ContainsKey('EncryptedFilePath')) 35 | { 36 | $EncryptedFilePath = Join-Path $Path "$StoreName.psd1.encrypted" 37 | } 38 | 39 | Write-Verbose "Decrypting $EncryptedFilePath." 40 | $DecryptedDataFile = ConvertFrom-EncryptedFile -path $EncryptedFilePath -CertificatePath $LocalCertificatePath 41 | 42 | Write-Verbose "Loading $($DecryptedDataFile.BaseName) into Credentials." 43 | $Credentials = (Get-Hashtable $DecryptedDataFile.FullName) 44 | 45 | Remove-PlainTextPassword $DecryptedDataFile.FullName 46 | 47 | if ($PSBoundParameters.ContainsKey('UserName')) 48 | { 49 | $CredentialsToReturn = @{} 50 | foreach ($User in $UserName) 51 | { 52 | $CredentialsToReturn.Add($User,$Credentials[$User]) 53 | } 54 | return $CredentialsToReturn 55 | } 56 | else 57 | { 58 | return $Credentials 59 | } 60 | } 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /Tooling/DscOperations/Invoke-DscPull.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-DscPull 2 | { 3 | <# 4 | .Synopsis 5 | Forces a consistency check on a targeted server. 6 | .Description 7 | Forces a consistency check on a targeted server. The LCM will run local consistency checks. 8 | In order to force the LCM to pull a new configuration, you many need to run this command multiple times, 9 | as the configuration will pull from the pull server on every "x" local check (where the minimum number of checks is 2). 10 | .Example 11 | Invoke-DscPull -ComputerName OR-WEB01 -Verbose 12 | .Example 13 | 1..7 | Invoke-DscPull -ComputerName {"or-web0$_"} 14 | .Example 15 | Invoke-DscPull -Computer OR-WEB01 -Verbose -Force 16 | .Example 17 | Invoke-DscPull OR-WEB01 -verbose 18 | 19 | #> 20 | param ( 21 | # 22 | [parameter(ValueFromPipeline=$true, Position=0)] 23 | [alias('ComputerName', 'Name', '__Server')] 24 | $CimSession = $null, 25 | [switch] 26 | $Force 27 | ) 28 | Begin { 29 | $Flag = 3 30 | if ($PSBoundParameters.ContainsKey('Flag')) { 31 | $PSBoundParameters.Remove('Flag') | Out-Null 32 | } 33 | if ($PSBoundParameters.ContainsKey('Force')) { 34 | $PSBoundParameters.Remove('Force') | Out-Null 35 | } 36 | } 37 | Process { 38 | 39 | if ($Force) 40 | { 41 | Write-Verbose "" 42 | Write-Verbose "Terminating any existing WmiPrvSE processes to make sure that there are no cached resources mucking about." 43 | Get-CimInstance -ClassName Win32_Process -Filter 'Name like "WmiPrvSE.exe"' @parameters | 44 | Invoke-CimMethod -MethodName 'Terminate' | 45 | Out-Null 46 | } 47 | 48 | $parameters = @{ 49 | Namespace = 'root/microsoft/windows/desiredstateconfiguration' 50 | Class = 'MSFT_DscLocalConfigurationManager' 51 | MethodName = 'PerformRequiredConfigurationChecks' 52 | Arguments = @{Flags=[uint32]$flag} 53 | } 54 | 55 | Write-Verbose "" 56 | Write-Verbose "Forcing a consistency check on $CimSession" 57 | Invoke-CimMethod @parameters @PSBoundParameters 58 | } 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /Tooling/dscbuild/New-DscZipFile.ps1: -------------------------------------------------------------------------------- 1 | function New-DscZipFile 2 | { 3 | param( 4 | ## The name of the zip archive to create 5 | [parameter(ValueFromPipelineByPropertyName)] 6 | [alias('Name')] 7 | [string] 8 | $ZipFile, 9 | 10 | ## The name of the folder to archive 11 | [parameter(ValueFromPipelineByPropertyName)] 12 | [alias('FullName')] 13 | [string] 14 | $Path, 15 | 16 | ## Switch to delete the zip archive if it already exists. 17 | [Switch] $Force 18 | ) 19 | 20 | 21 | begin 22 | { 23 | [Byte[]] $zipHeader = 0x50,0x4B,0x05,0x06,0x00,0x00,0x00,0x00, 24 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 25 | 0x00,0x00,0x00,0x00,0x00,0x00 26 | } 27 | process 28 | { 29 | ## Create the Zip File 30 | $Version = Get-DscResourceVersion $path 31 | $folderName = $ZipFile + "_"+ $Version 32 | 33 | $ZipName = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$folderName.zip") 34 | Write-Verbose "Packing $path to to $ZipName." 35 | 36 | ## Check if the file exists already. If it does, check 37 | ## for -Force - generate an error if not specified. 38 | if(Test-Path $zipName) 39 | { 40 | if($Force) 41 | { 42 | Write-Verbose "Removing previous $zipname" 43 | Remove-Item $zipName -Force 44 | } 45 | else 46 | { 47 | throw "Item with specified name $zipName already exists." 48 | } 49 | } 50 | 51 | try 52 | { 53 | $shellObject = New-Object -comobject "Shell.Application" 54 | 55 | Write-Verbose "Creating new zip file $ZipName." 56 | $Writer = New-Object System.IO.FileStream $ZipName, "Create" 57 | $Writer.Write($zipheader, 0, 22) 58 | $Writer.Close(); 59 | 60 | Start-Sleep -Seconds 1 61 | $ZipFileObject = $shellObject.namespace($ZipName) 62 | 63 | Write-Verbose "Loading the zip file contents." 64 | $ZipFileObject.CopyHere($Path) 65 | Start-Sleep -Seconds 5 66 | } 67 | finally 68 | { 69 | ## Release the shell object 70 | $shellObject = $null 71 | $ZipFileObject = $null 72 | } 73 | get-item $ZipName 74 | } 75 | } 76 | 77 | 78 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Assert-DestinationDirectory.ps1: -------------------------------------------------------------------------------- 1 | function Assert-DestinationDirectory { 2 | [cmdletbinding(SupportsShouldProcess=$true)] 3 | param () 4 | 5 | if ( Test-BuildResource ) { 6 | 7 | if ($pscmdlet.shouldprocess('module folders')) {} 8 | Add-DscBuildParameter -Name DestinationModuleDirectory -value (join-path $script:DscBuildParameters.DestinationRootDirectory 'Modules') @psboundparameters 9 | Add-DscBuildParameter -Name ModuleOutputPath -value (join-path $script:DscBuildParameters.WorkingDirectory 'BuildOutput\Modules') @psboundparameters 10 | Add-DscBuildParameter -Name DestinationToolsDirectory -value $script:DscBuildParameters.DestinationToolDirectory @psboundparameters 11 | 12 | Assert-Directory -path $script:DscBuildParameters.DestinationToolsDirectory @psboundparameters 13 | Assert-Directory -path $script:DscBuildParameters.DestinationModuleDirectory @psboundparameters 14 | Assert-Directory -path $script:DscBuildParameters.ModuleOutputPath @psboundparameters 15 | } 16 | 17 | if ( Test-BuildConfiguration) { 18 | if ($pscmdlet.shouldprocess('configuration folders')) {} 19 | Add-DscBuildParameter -Name DestinationConfigurationDirectory -value (join-path $script:DscBuildParameters.DestinationRootDirectory 'Configuration') @psboundparameters 20 | Add-DscBuildParameter -Name ConfigurationOutputPath -value (join-path $script:DscBuildParameters.WorkingDirectory 'BuildOutput\Configuration') @psboundparameters 21 | 22 | Assert-Directory -path $script:DscBuildParameters.DestinationConfigurationDirectory @psboundparameters 23 | Assert-Directory -path $script:DscBuildParameters.ConfigurationOutputPath @psboundparameters 24 | } 25 | 26 | if ( Test-BuildTools ) { 27 | if ($pscmdlet.shouldprocess('tools folders')) {} 28 | Add-DscBuildParameter -Name DestinationToolsDirectory -value $script:DscBuildParameters.DestinationToolDirectory @psboundparameters 29 | Assert-Directory -path $script:DscBuildParameters.DestinationToolsDirectory @psboundparameters 30 | } 31 | 32 | } 33 | 34 | function Assert-Directory { 35 | [cmdletbinding(SupportsShouldProcess=$true)] 36 | param ( 37 | $Path 38 | ) 39 | 40 | try { 41 | if (-not (Test-Path $path -ea Stop)) { 42 | $output = mkdir @psboundparameters 43 | } 44 | } 45 | catch { 46 | Write-Warning "Failed to validate $path" 47 | throw $_.Exception 48 | } 49 | } 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/DscConfiguration.psm1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | [string] 4 | $ConfigurationDataPath, 5 | [string] 6 | $LocalCertificateThumbprint = "$((Get-DscLocalConfigurationManager).CertificateId)" 7 | ) 8 | 9 | 10 | $LocalCertificatePath = "cert:\LocalMachine\My\$LocalCertificateThumbprint" 11 | $ConfigurationData = @{AllNodes=@(); Credentials=@{}; Applications=@{}; Services=@{}; SiteData =@{}} 12 | 13 | . $psscriptroot\Get-Hashtable.ps1 14 | . $psscriptroot\Test-LocalCertificate.ps1 15 | . $psscriptroot\Add-NodeRoleFromServiceConfigurationData.ps1 16 | 17 | . $psscriptroot\New-ConfigurationDataStore 18 | . $psscriptroot\New-DscNodeMetadata.ps1 19 | 20 | . $psscriptroot\Get-AllNodesConfigurationData.ps1 21 | . $psscriptroot\Get-ConfigurationData.ps1 22 | . $psscriptroot\Get-CredentialConfigurationData.ps1 23 | . $psscriptroot\Get-ApplicationConfigurationData.ps1 24 | . $psscriptroot\Get-ServiceConfigurationData.ps1 25 | . $psscriptroot\Get-SiteDataConfigurationData.ps1 26 | . $psscriptroot\Get-EncryptedPassword.ps1 27 | 28 | . $psscriptroot\Add-EncryptedPassword.ps1 29 | . $psscriptroot\ConvertFrom-EncryptedFile.ps1 30 | . $psscriptroot\ConvertTo-CredentialLookup.ps1 31 | . $psscriptroot\ConvertTo-EncryptedFile.ps1 32 | . $psscriptroot\New-Credential.ps1 33 | . $psscriptroot\Remove-PlainTextPassword.ps1 34 | 35 | 36 | function Set-DscConfigurationDataPath { 37 | param ( 38 | [parameter(Mandatory)] 39 | [ValidateNotNullOrEmpty()] 40 | [string] 41 | $Path 42 | ) 43 | 44 | $script:ConfigurationDataPath = (Resolve-path $Path).Path 45 | } 46 | Set-Alias -Name 'Set-ConfigurationDataPath' -Value 'Set-DscConfigurationDataPath' 47 | 48 | function Get-DscConfigurationDataPath { 49 | 50 | $script:ConfigurationDataPath 51 | } 52 | Set-Alias -Name 'Get-ConfigurationDataPath' -Value 'Get-DscConfigurationDataPath' 53 | 54 | function Resolve-DscConfigurationDataPath { 55 | param ( 56 | [parameter()] 57 | [string] 58 | $Path 59 | ) 60 | 61 | if ( -not ($psboundparameters.containskey('Path')) ) { 62 | if ([string]::isnullorempty($script:ConfigurationDataPath)) { 63 | if (test-path $env:ConfigurationDataPath) { 64 | $path = $env:ConfigurationDataPath 65 | } 66 | } 67 | else { 68 | $path = $script:ConfigurationDataPath 69 | } 70 | } 71 | 72 | if ( -not ([string]::isnullorempty($path)) ) { 73 | Set-DscConfigurationDataPath -path $path 74 | } 75 | 76 | } 77 | Set-Alias -Name 'Resolve-ConfigurationDataPath' -Value 'Resolve-DscConfigurationDataPath' -------------------------------------------------------------------------------- /Tooling/DscConfiguration/Get-ConfigurationData.ps1: -------------------------------------------------------------------------------- 1 | function Get-DscConfigurationData 2 | { 3 | [cmdletbinding(DefaultParameterSetName='NoFilter')] 4 | param ( 5 | [parameter()] 6 | [ValidateNotNullOrEmpty()] 7 | [string] $Path, 8 | 9 | [parameter(ParameterSetName = 'NameFilter')] 10 | [string] $Name, 11 | 12 | [parameter(ParameterSetName = 'NodeNameFilter')] 13 | [string] $NodeName, 14 | 15 | [parameter(ParameterSetName = 'RoleFilter')] 16 | [string] $Role, 17 | 18 | [parameter()] 19 | [switch] $Force 20 | ) 21 | 22 | begin { 23 | 24 | if (($script:ConfigurationData -eq $null) -or $force) { 25 | $script:ConfigurationData = @{ AllNodes = @(); SiteData = @{}; Applications = @{}; Services=@{}; Credentials = @{} } 26 | } 27 | 28 | $ResolveConfigurationDataPathParams = @{} 29 | if ($psboundparameters.containskey('path')) { 30 | $ResolveConfigurationDataPathParams.Path = $path 31 | } 32 | Resolve-ConfigurationDataPath @ResolveConfigurationDataPathParams 33 | 34 | } 35 | end { 36 | 37 | Get-AllNodesConfigurationData 38 | 39 | Write-Verbose 'Checking for filters of AllNodes.' 40 | switch ($PSCmdlet.ParameterSetName) 41 | { 42 | 'NameFilter' { 43 | Write-Verbose "Filtering for nodes with the Name $Name" 44 | $script:ConfigurationData.AllNodes = $script:ConfigurationData.AllNodes.Where({$_.Name -like $Name}) 45 | } 46 | 'NodeNameFilter' { 47 | Write-Verbose "Filtering for nodes with the GUID of $NodeName" 48 | $script:ConfigurationData.AllNodes = $script:ConfigurationData.AllNodes.Where({$_.NodeName -like $NodeName}) 49 | } 50 | 'RoleFilter' { 51 | Write-Verbose "Filtering for nodes with the Role of $Role" 52 | $script:ConfigurationData.AllNodes = $script:ConfigurationData.AllNodes.Where({ $_.roles -contains $Role}) 53 | } 54 | default { 55 | Write-Verbose 'Loading Site Data' 56 | Get-SiteDataConfigurationData 57 | Write-Verbose 'Loading Services Data' 58 | Get-ServiceConfigurationData 59 | Write-Verbose 'Loading Credential Data' 60 | Get-CredentialConfigurationData 61 | Write-Verbose 'Loading Application Data' 62 | Get-ApplicationConfigurationData 63 | } 64 | } 65 | 66 | Add-NodeRoleFromServiceConfigurationData 67 | return $script:ConfigurationData 68 | } 69 | } 70 | 71 | Set-Alias -Name 'Get-ConfigurationData' -Value 'Get-DscConfigurationData' 72 | -------------------------------------------------------------------------------- /Tooling/DscOperations/Clear-DscEventLog.ps1: -------------------------------------------------------------------------------- 1 | function Clear-DSCEventLog 2 | { 3 | param ($session) 4 | icm $session { 5 | function Clear-WinEvent 6 | { 7 | [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High',DefaultParameterSetName="LogName")] 8 | 9 | param( 10 | [Parameter( 11 | Position=0, 12 | Mandatory=$true, 13 | ParameterSetName="LogName", 14 | ValueFromPipeline=$true, 15 | ValueFromPipelineByPropertyName=$true 16 | )] 17 | [String[]]$LogName, 18 | 19 | [Parameter( 20 | Position=0, 21 | Mandatory=$true, 22 | ParameterSetName="EventLogConfiguration", 23 | ValueFromPipeline=$true 24 | )] 25 | [System.Diagnostics.Eventing.Reader.EventLogConfiguration[]]$EventLog, 26 | 27 | [switch]$Force 28 | 29 | ) 30 | 31 | 32 | process 33 | { 34 | switch($PSCmdlet.ParameterSetName) 35 | { 36 | 'LogName' 37 | { 38 | Write-Verbose "ParameterSetName=LogName" 39 | foreach($l in $LogName) 40 | { 41 | if($Force -or $PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Clear Event log '$l'")) 42 | { 43 | [System.Diagnostics.Eventing.Reader.EventLogSession]::GlobalSession.ClearLog($l) 44 | } 45 | } 46 | } 47 | 48 | 'EventLogConfiguration' 49 | { 50 | Write-Verbose "ParameterSetName=EventLogConfiguration" 51 | foreach($l in $EventLog) 52 | { 53 | if($Force -or $PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Clear Event log '$($l.LogName)'")) 54 | { 55 | [System.Diagnostics.Eventing.Reader.EventLogSession]::GlobalSession.ClearLog($l.LogName) 56 | } 57 | } 58 | 59 | } 60 | } 61 | } 62 | } 63 | Get-WinEvent -ListLog Microsoft-Windows-DSC/Operational -Force | 64 | clear-winevent -force 65 | 66 | } 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Get-DscResourceVersion.ps1: -------------------------------------------------------------------------------- 1 | function Get-ModuleVersion 2 | { 3 | param ( 4 | [parameter(mandatory)] 5 | [validatenotnullorempty()] 6 | [string] 7 | $path, 8 | [switch] 9 | $asVersion 10 | ) 11 | $ModuleName = split-path $path -Leaf 12 | $ModulePSD1 = join-path $path "$ModuleName.psd1" 13 | 14 | Write-Verbose '' 15 | Write-Verbose "Checking for $ModulePSD1" 16 | if (Test-Path $ModulePSD1) 17 | { 18 | $psd1 = get-content $ModulePSD1 -Raw 19 | $Version = (Invoke-Expression -Command $psd1)['ModuleVersion'] 20 | Write-Verbose "Found version $Version for $ModuleName." 21 | Write-Verbose '' 22 | if ($asVersion) { 23 | [Version]::parse($Version) 24 | } 25 | else { 26 | return $Version 27 | } 28 | } 29 | else 30 | { 31 | Write-Warning "Could not find a PSD1 for $modulename at $ModulePSD1." 32 | } 33 | } 34 | 35 | 36 | function Get-ModuleAuthor 37 | { 38 | param ( 39 | [parameter(mandatory)] 40 | [validatenotnullorempty()] 41 | [string] 42 | $path 43 | ) 44 | $ModuleName = split-path $path -Leaf 45 | $ModulePSD1 = join-path $path "$ModuleName.psd1" 46 | 47 | if (Test-Path $ModulePSD1) 48 | { 49 | $psd1 = get-content $ModulePSD1 -Raw 50 | $Author = (Invoke-Expression -Command $psd1)['Author'] 51 | Write-Verbose "Found author $Author for $ModuleName." 52 | return $Author 53 | } 54 | else 55 | { 56 | Write-Warning "Could not find a PSD1 for $modulename at $ModulePSD1." 57 | } 58 | } 59 | 60 | New-Alias -Name Get-DscResourceVersion -Value Get-ModuleVersion -Force 61 | 62 | 63 | function Test-ModuleVersion { 64 | param ( 65 | [parameter(ValueFromPipeline, Mandatory)] 66 | [object] 67 | $InputObject, 68 | [parameter(Mandatory, position = 0)] 69 | [string] 70 | $Destination 71 | ) 72 | process { 73 | $DestinationModule = join-path $Destination $InputObject.name 74 | 75 | if (test-path $DestinationModule) { 76 | $CurrentModuleVersion = Get-ModuleVersion -Path $DestinationModule -asVersion 77 | $NewModuleVersion = Get-ModuleVersion -Path $InputObject.fullname -asVersion 78 | if (($CurrentModuleVersion -eq $null) -or ($NewModuleVersion -gt $CurrentModuleVersion)) { 79 | Write-Verbose "New module version is higher the the currently deployed module. Replacing it." 80 | $InputObject 81 | } 82 | else { 83 | Write-Verbose "The current module is the same version or newer than the one in source control. Not replacing it." 84 | } 85 | } 86 | else { 87 | Write-Verbose "No existing module at $DestinationModule." 88 | $InputObject 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /Tooling/DscOperations/DscOperations.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'DscOperations' 3 | # 4 | # Generated by: Steven 5 | # 6 | # Generated on: 4/9/2014 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'DscOperations.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.5.5.0' 16 | 17 | # ID used to uniquely identify this module 18 | GUID = 'c5e2c892-c16d-4649-9d65-dc14c0bddcb9' 19 | 20 | # Author of this module 21 | Author = 'Steven Murawski' 22 | 23 | # Company or vendor of this module 24 | CompanyName = 'Stack Exchange' 25 | 26 | # Copyright statement for this module 27 | Copyright = '(c) 2014 Steven Murawski. All rights reserved.' 28 | 29 | # Description of the functionality provided by this module 30 | Description = 'Commands for operating a DSC Pull Server environment.' 31 | 32 | # Minimum version of the Windows PowerShell engine required by this module 33 | # PowerShellVersion = '' 34 | 35 | # Name of the Windows PowerShell host required by this module 36 | # PowerShellHostName = '' 37 | 38 | # Minimum version of the Windows PowerShell host required by this module 39 | # PowerShellHostVersion = '' 40 | 41 | # Minimum version of Microsoft .NET Framework required by this module 42 | # DotNetFrameworkVersion = '' 43 | 44 | # Minimum version of the common language runtime (CLR) required by this module 45 | # CLRVersion = '' 46 | 47 | # Processor architecture (None, X86, Amd64) required by this module 48 | # ProcessorArchitecture = '' 49 | 50 | # Modules that must be imported into the global environment prior to importing this module 51 | RequiredModules = @('DscConfiguration', 'DscBuild') 52 | 53 | # Assemblies that must be loaded prior to importing this module 54 | # RequiredAssemblies = @() 55 | 56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 57 | # ScriptsToProcess = @() 58 | 59 | # Type files (.ps1xml) to be loaded when importing this module 60 | # TypesToProcess = @() 61 | 62 | # Format files (.ps1xml) to be loaded when importing this module 63 | # FormatsToProcess = @() 64 | 65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 66 | # NestedModules = @() 67 | 68 | # Functions to export from this module 69 | FunctionsToExport = 'Clear-DscEventLog', 'Clear-DscTemporaryModule', 'Invoke-DscPull', 'Set-DscClient', 'Invoke-DscApply' 70 | 71 | # Cmdlets to export from this module 72 | #CmdletsToExport = '*' 73 | 74 | # Variables to export from this module 75 | #VariablesToExport = '*' 76 | 77 | # Aliases to export from this module 78 | #AliasesToExport = '*' 79 | 80 | # List of all modules packaged with this module 81 | # ModuleList = @() 82 | 83 | # List of all files packaged with this module 84 | # FileList = @() 85 | 86 | # Private data to pass to the module specified in RootModule/ModuleToProcess 87 | # PrivateData = '' 88 | 89 | # HelpInfo URI of this module 90 | # HelpInfoURI = '' 91 | 92 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 93 | # DefaultCommandPrefix = '' 94 | 95 | } 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Where-DscResource.ps1: -------------------------------------------------------------------------------- 1 | function Where-DscResource 2 | { 3 | param ( 4 | [parameter(ValueFromPipeline)] 5 | [IO.FileSystemInfo] 6 | $InputObject, 7 | [parameter()] 8 | [string] 9 | $Destination, 10 | [switch] 11 | [alias('IsValid')] 12 | $CheckIfIsValid 13 | ) 14 | begin 15 | { 16 | if ($CheckIfIsValid) 17 | { 18 | $AllResources = Get-DscResource | 19 | Where-Object {$_.implementedas -like 'PowerShell'} 20 | 21 | Add-DscBuildParameter -Name TestedModules -value @() 22 | } 23 | } 24 | process 25 | { 26 | Write-Verbose "Checking $($inputobject.Name)." 27 | if ( $CheckIfIsValid -and (Test-DscModuleResourceIsValid @psboundparameters) ) 28 | { 29 | $InputObject 30 | } 31 | } 32 | } 33 | 34 | function Test-DscModuleResourceIsValid 35 | { 36 | [cmdletbinding()] 37 | param ( 38 | [parameter(ValueFromPipeline)] 39 | [IO.FileSystemInfo] 40 | $InputObject, 41 | [parameter()] 42 | [string] 43 | $Destination, 44 | [switch] 45 | [alias('Changed')] 46 | $CheckIfChanged, 47 | [switch] 48 | [alias('IsValid')] 49 | $CheckIfIsValid 50 | ) 51 | 52 | Write-Verbose "Testing for valid resources." 53 | $FailedDscResources = Get-FailedDscResource -AllModuleResources (Get-DscResourceForModule -InputObject $InputObject) 54 | 55 | if ($FailedDscResources) 56 | { 57 | Write-Verbose "Found failed resources." 58 | foreach ($resource in $FailedDscResources) 59 | { 60 | Write-Warning "`t`tFailed Resource - $($resource.Name)" 61 | } 62 | throw "Fix invalid resources in $($InputObject.Name)." 63 | } 64 | 65 | return $true 66 | } 67 | 68 | function Get-DscResourceForModule 69 | { 70 | [cmdletbinding()] 71 | param ($InputObject) 72 | 73 | $Name = $inputobject.Name 74 | Write-Verbose "Retrieving all resources and filtering for $Name." 75 | 76 | $ResourcesInModule = $AllResources | 77 | Where-Object { 78 | Write-Verbose "`tChecking for $($_.name) in $name." 79 | $_.module -like $name 80 | } | 81 | ForEach-Object { 82 | Write-Verbose "`t$Name contains $($_.Name)." 83 | $_ 84 | } 85 | if ($ResourcesInModule.count -eq 0) 86 | { 87 | Write-Warning "$Name does not contain any resources." 88 | } 89 | else { 90 | $script:DscBuildParameters.TestedModules += $InputObject.FullName 91 | } 92 | $ResourcesInModule 93 | } 94 | 95 | function Get-FailedDscResource 96 | { 97 | [cmdletbinding()] 98 | param ($AllModuleResources) 99 | 100 | foreach ($resource in $AllModuleResources) 101 | { 102 | if (-not (Test-cDscResource -Name $Resource.Name)) 103 | { 104 | Write-Warning "`tResources $($_.name) is invalid." 105 | $resource 106 | } 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /Tooling/dscbuild/DscBuild.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'DscBuild' 3 | # 4 | # Generated by: Steven Murawski 5 | # 6 | # Generated on: 3/27/2014 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'DscBuild.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.6.5.5' 16 | 17 | # ID used to uniquely identify this module 18 | GUID = '23ccd4bf-0a52-4077-986f-c153893e5a6a' 19 | 20 | # Author of this module 21 | Author = 'Steven Murawski' 22 | 23 | # Company or vendor of this module 24 | CompanyName = 'Stack Exchange' 25 | 26 | # Copyright statement for this module 27 | Copyright = '(c) 2014 Steven Murawski. All rights reserved.' 28 | 29 | # Description of the functionality provided by this module 30 | Description = 'Build tools for DSC Resources and Configurations' 31 | 32 | # Minimum version of the Windows PowerShell engine required by this module 33 | PowerShellVersion = '4.0' 34 | 35 | # Name of the Windows PowerShell host required by this module 36 | # PowerShellHostName = '' 37 | 38 | # Minimum version of the Windows PowerShell host required by this module 39 | # PowerShellHostVersion = '' 40 | 41 | # Minimum version of Microsoft .NET Framework required by this module 42 | # DotNetFrameworkVersion = '' 43 | 44 | # Minimum version of the common language runtime (CLR) required by this module 45 | # CLRVersion = '' 46 | 47 | # Processor architecture (None, X86, Amd64) required by this module 48 | # ProcessorArchitecture = '' 49 | 50 | # Modules that must be imported into the global environment prior to importing this module 51 | RequiredModules = @('Pester', 'cDscResourceDesigner') 52 | 53 | # Assemblies that must be loaded prior to importing this module 54 | # RequiredAssemblies = @() 55 | 56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 57 | # ScriptsToProcess = @() 58 | 59 | # Type files (.ps1xml) to be loaded when importing this module 60 | # TypesToProcess = @() 61 | 62 | # Format files (.ps1xml) to be loaded when importing this module 63 | # FormatsToProcess = @() 64 | 65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 66 | # NestedModules = @() 67 | 68 | # Functions to export from this module 69 | FunctionsToExport = 'Invoke-DscBuild', 70 | 'Get-DscResourceWmiClass', 71 | 'Remove-DscResourceWmiClass', 72 | 'Resolve-DscConfigurationProperty' 73 | 74 | # Cmdlets to export from this module 75 | #CmdletsToExport = '*' 76 | 77 | # Variables to export from this module 78 | #VariablesToExport = '*' 79 | 80 | # Aliases to export from this module 81 | # AliasesToExport = 'Resolve-ConfigurationProperty' 82 | 83 | # List of all modules packaged with this module 84 | # ModuleList = @() 85 | 86 | # List of all files packaged with this module 87 | # FileList = @() 88 | 89 | # Private data to pass to the module specified in RootModule/ModuleToProcess 90 | # PrivateData = '' 91 | 92 | # HelpInfo URI of this module 93 | # HelpInfoURI = '' 94 | 95 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 96 | # DefaultCommandPrefix = '' 97 | 98 | } 99 | 100 | 101 | -------------------------------------------------------------------------------- /Tooling/DscDevelopment/DscDevelopment.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'DscDevelopment' 3 | # 4 | # Generated by: Steven Murawski 5 | # 6 | # Generated on: 4/9/2014 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'DscDevelopment.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.5.1.0' 16 | 17 | # ID used to uniquely identify this module 18 | GUID = 'e51973fd-3e2b-46ef-b307-66b02811db52' 19 | 20 | # Author of this module 21 | Author = 'Steven Murawski' 22 | 23 | # Company or vendor of this module 24 | CompanyName = 'Stack Exchange' 25 | 26 | # Copyright statement for this module 27 | Copyright = '(c) 2014 Steven Murawski. All rights reserved.' 28 | 29 | # Description of the functionality provided by this module 30 | # Description = '' 31 | 32 | # Minimum version of the Windows PowerShell engine required by this module 33 | # PowerShellVersion = '' 34 | 35 | # Name of the Windows PowerShell host required by this module 36 | # PowerShellHostName = '' 37 | 38 | # Minimum version of the Windows PowerShell host required by this module 39 | # PowerShellHostVersion = '' 40 | 41 | # Minimum version of Microsoft .NET Framework required by this module 42 | # DotNetFrameworkVersion = '' 43 | 44 | # Minimum version of the common language runtime (CLR) required by this module 45 | # CLRVersion = '' 46 | 47 | # Processor architecture (None, X86, Amd64) required by this module 48 | # ProcessorArchitecture = '' 49 | 50 | # Modules that must be imported into the global environment prior to importing this module 51 | RequiredModules = @('DscBuild', 'DscConfiguration') 52 | 53 | # Assemblies that must be loaded prior to importing this module 54 | # RequiredAssemblies = @() 55 | 56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 57 | # ScriptsToProcess = @() 58 | 59 | # Type files (.ps1xml) to be loaded when importing this module 60 | # TypesToProcess = @() 61 | 62 | # Format files (.ps1xml) to be loaded when importing this module 63 | # FormatsToProcess = @() 64 | 65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 66 | # NestedModules = @() 67 | 68 | # Functions to export from this module 69 | FunctionsToExport = 'Add-CachedCimClass', 70 | 'Get-CachedCimClass', 71 | 'Get-Hashtable', 72 | 'Import-CimInstance', 73 | 'New-cDscCompositeResource', 74 | 'New-DscResourceFromModule', 75 | 'New-DscResourceShell', 76 | 'New-MofFile', 77 | 'Reset-CimClassCache', 78 | 'Test-DscBuild', 79 | 'Test-MofFile' 80 | 81 | # Cmdlets to export from this module 82 | # CmdletsToExport = '*' 83 | 84 | # Variables to export from this module 85 | # VariablesToExport = '*' 86 | 87 | # Aliases to export from this module 88 | # AliasesToExport = '*' 89 | 90 | # List of all modules packaged with this module 91 | # ModuleList = @() 92 | 93 | # List of all files packaged with this module 94 | # FileList = @() 95 | 96 | # Private data to pass to the module specified in RootModule/ModuleToProcess 97 | # PrivateData = '' 98 | 99 | # HelpInfo URI of this module 100 | # HelpInfoURI = '' 101 | 102 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 103 | # DefaultCommandPrefix = '' 104 | 105 | } 106 | 107 | 108 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Invoke-DscConfiguration.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-DscConfiguration { 2 | [cmdletbinding(SupportsShouldProcess=$true)] 3 | param () 4 | 5 | if ( Test-BuildConfiguration ) 6 | { 7 | if ( $pscmdlet.shouldprocess("Configuration module $($script:DscBuildParameters.ConfigurationModuleName) and configuration $($script:DscBuildParameters.ConfigurationName)") ) { 8 | Write-Verbose '' 9 | Write-Verbose "Importing configuration module: $($script:DscBuildParameters.ConfigurationModuleName)" 10 | if (Get-Module -list -name "$($script:DscBuildParameters.ConfigurationModuleName)") { 11 | 12 | $ResetVerbosePreference = $false 13 | if ($PSBoundParameters.ContainsKey('Verbose') -and $PSBoundParameters['Verbose'].IsPresent) { 14 | $ResetVerbosePreference = $true 15 | $VerbosePreference = 'SilentlyContinue' 16 | } 17 | 18 | try { 19 | import-module -name "$($script:DscBuildParameters.ConfigurationModuleName)" -force -Verbose:$false -ErrorAction Stop 20 | } 21 | catch { 22 | 23 | Write-Warning "Failed to load configuration module" 24 | $Exception = $_.Exception 25 | do { 26 | Write-Warning "`t$($_.Message)" 27 | $Exception = $_.InnerException 28 | } while ($Exception -ne $null) 29 | throw "Failed to load $($script:DscBuildParameters.ConfigurationModuleName)" 30 | } 31 | 32 | if ($ResetVerbosePreference) { 33 | $VerbosePreference = 'Continue' 34 | } 35 | Write-Verbose "Imported $($script:DscBuildParameters.ConfigurationModuleName)" 36 | Write-Verbose '' 37 | } 38 | else { 39 | Write-Warning "Unable to resolve the module '$($script:DscBuildParameters.ConfigurationModuleName)'" 40 | Write-Warning "Current modules on PSModulePath" 41 | $env:psmodulepath -split ';' | 42 | get-childitem -directory | 43 | foreach { 44 | Write-Warning "`tFound $($_.Name)" 45 | } 46 | throw "Failed to load configuration module" 47 | } 48 | 49 | try 50 | { 51 | Write-Verbose "" 52 | Write-Verbose 'Starting to generate configurations.' 53 | Write-Verbose "`tWriting configurations to $($script:DscBuildParameters.ConfigurationOutputPath)" 54 | $ErrorActionPreference = 'Stop' 55 | 56 | $output = . $script:DscBuildParameters.ConfigurationName -outputpath $script:DscBuildParameters.ConfigurationOutputPath -ConfigurationData $script:DscBuildParameters.ConfigurationData 57 | 58 | Write-Verbose "Done creating configurations. Get ready for some pullin' baby!" 59 | Write-Verbose "" 60 | } 61 | catch 62 | { 63 | Write-Warning 'Failed to generate configs.' 64 | throw 'Failed to generate configs.' 65 | } 66 | 67 | 68 | remove-module $script:DscBuildParameters.ConfigurationModuleName 69 | } 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/DscConfiguration.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'DscConfiguration' 3 | # 4 | # Generated by: Steven 5 | # 6 | # Generated on: 4/14/2014 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'DscConfiguration.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.5.1.0' 16 | 17 | # ID used to uniquely identify this module 18 | GUID = '0b48e2a7-dcd7-4888-95ec-f80d6fca03b6' 19 | 20 | # Author of this module 21 | Author = 'Steven Murawski' 22 | 23 | # Company or vendor of this module 24 | CompanyName = 'Stack Exchange' 25 | 26 | # Copyright statement for this module 27 | Copyright = '(c) 2014 Steven. All rights reserved.' 28 | 29 | # Description of the functionality provided by this module 30 | # Description = '' 31 | 32 | # Minimum version of the Windows PowerShell engine required by this module 33 | # PowerShellVersion = '' 34 | 35 | # Name of the Windows PowerShell host required by this module 36 | # PowerShellHostName = '' 37 | 38 | # Minimum version of the Windows PowerShell host required by this module 39 | # PowerShellHostVersion = '' 40 | 41 | # Minimum version of Microsoft .NET Framework required by this module 42 | # DotNetFrameworkVersion = '' 43 | 44 | # Minimum version of the common language runtime (CLR) required by this module 45 | # CLRVersion = '' 46 | 47 | # Processor architecture (None, X86, Amd64) required by this module 48 | # ProcessorArchitecture = '' 49 | 50 | # Modules that must be imported into the global environment prior to importing this module 51 | # RequiredModules = @() 52 | 53 | # Assemblies that must be loaded prior to importing this module 54 | # RequiredAssemblies = @() 55 | 56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 57 | # ScriptsToProcess = @() 58 | 59 | # Type files (.ps1xml) to be loaded when importing this module 60 | # TypesToProcess = @() 61 | 62 | # Format files (.ps1xml) to be loaded when importing this module 63 | # FormatsToProcess = @() 64 | 65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 66 | # NestedModules = @() 67 | 68 | # Functions to export from this module 69 | FunctionsToExport = 'Get-DscConfigurationData', 70 | 'New-DscNodeMetadata', 71 | 'New-DscServiceMetadata', 72 | 'New-DscSiteMetadata', 73 | 'Add-DscEncryptedPassword', 74 | 'Get-DscEncryptedPassword', 75 | 'Set-DscConfigurationDataPath', 76 | 'Get-DscConfigurationDataPath', 77 | 'New-DscConfigurationDataStore' 78 | 79 | # Cmdlets to export from this module 80 | #CmdletsToExport = '*' 81 | 82 | # Variables to export from this module 83 | #VariablesToExport = '*' 84 | 85 | # Aliases to export from this module 86 | AliasesToExport = 'Add-EncryptedPassword', 87 | 'Set-ConfigurationDataPath', 88 | 'Get-ConfigurationDataPath' 89 | 90 | # List of all modules packaged with this module 91 | # ModuleList = @() 92 | 93 | # List of all files packaged with this module 94 | # FileList = @() 95 | 96 | # Private data to pass to the module specified in RootModule/ModuleToProcess 97 | # PrivateData = '' 98 | 99 | # HelpInfo URI of this module 100 | # HelpInfoURI = '' 101 | 102 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 103 | # DefaultCommandPrefix = '' 104 | 105 | } 106 | 107 | 108 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Where-DscResource.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.ps1", ".ps1") 3 | $pathtosut = join-path $here $sut 4 | 5 | iex ( gc $pathtosut -Raw ) 6 | 7 | <# 8 | Describe 'how Test-ZippedModuleChanged reponds' { 9 | $Setup = { 10 | mkdir testdrive:\Source -erroraction silentlycontinue | out-null 11 | $Source = get-item testdrive:\source | select -expand fullname 12 | 13 | mkdir testdrive:\Destination -erroraction silentlycontinue | out-null 14 | $Destination = get-item testdrive:\Destination | select -expand fullname 15 | 16 | $InputObjectProperties = @{ 17 | Name = 'MyCustomResource' 18 | fullname = (join-path $Source 'MyCustomResource') 19 | } 20 | 21 | $InputObject = [pscustomobject]$InputObjectProperties 22 | 23 | mock -command Get-FileHash -parameterFilter {$path -like $InputObject.fullname} -mockwith {[pscustomobject]@{hash='123'}} 24 | } 25 | 26 | 27 | context "when the current version is the same as a previous version" { 28 | . $Setup 29 | mock -command test-path -mockwith {$true} 30 | mock -command Get-FileHash -parameterFilter {$path -like (Join-path $Destination 'MyCustomResource')} -mockwith {[pscustomobject]@{hash='123'}} 31 | 32 | $result = Test-ZippedModuleChanged 33 | 34 | it "should return false" { 35 | $result | should be $false 36 | } 37 | } 38 | 39 | context "when the current version is the different as a previous version" { 40 | . $Setup 41 | mock -command test-path -mockwith {$true} 42 | mock -command Get-FileHash -parameterFilter {$path -like (Join-path $Destination 'MyCustomResource')} -mockwith {[pscustomobject]@{hash='321'}} 43 | 44 | $result = Test-ZippedModuleChanged 45 | 46 | it "should return true" { 47 | $result | should be $true 48 | } 49 | } 50 | 51 | context "when no previous version exists" { 52 | . $Setup 53 | mock -command test-path -mockwith {$false} 54 | 55 | $result = Test-ZippedModuleChanged 56 | 57 | it "should return true" { 58 | $result | should be $true 59 | } 60 | } 61 | } 62 | #> 63 | 64 | Describe 'how Test-DscModuleResourceIsValid behaves' { 65 | 66 | context 'when there are failed DSC resources' { 67 | mock Get-DscResourceForModule -mockwith {} 68 | mock Get-FailedDscResource -mockwith { 69 | [pscustomobject]@{name='TestResource'}, 70 | [pscustomobject]@{name='SecondResource'} 71 | } 72 | 73 | it 'should throw an exception' { 74 | { Test-DscModuleResourceIsValid} | 75 | should throw 76 | } 77 | } 78 | 79 | context 'when there are no DSC resources' { 80 | mock Get-DscResourceForModule -mockwith {} 81 | mock Get-FailedDscResource -mockwith {} 82 | 83 | $result = Test-DscModuleResourceIsValid 84 | 85 | it 'should return true' { 86 | $result | should be $true 87 | } 88 | } 89 | 90 | context 'when all DSC resources are valid' { 91 | mock Get-DscResourceForModule -mockwith { 92 | [pscustomobject]@{name='TestResource'}, 93 | [pscustomobject]@{name='SecondResource'} 94 | } 95 | mock Get-FailedDscResource -mockwith {} 96 | 97 | $result = Test-DscModuleResourceIsValid 98 | 99 | it 'should return true' { 100 | $result | should be $true 101 | } 102 | } 103 | } 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /Tooling/DscDevelopment/New-DscCompositeResource.ps1: -------------------------------------------------------------------------------- 1 | Function New-DscCompositeResource 2 | { 3 | <# 4 | .Synopsis 5 | Short description of the command 6 | .Description 7 | Longer description of the command 8 | .EXAMPLE 9 | New-DscCompositeResource -Path "C:\TestModules" -ModuleName "Wakka" -ResourceName "Foo" 10 | .EXAMPLE 11 | New-DscCompositeResource -ModuleName "Wakka" -ResourceName "Foo" 12 | .EXAMPLE 13 | "Foo","Bar","Baz" | New-DscCompositeResource -ModuleName "Wakka" 14 | #> 15 | [CmdletBinding(SupportsShouldProcess=$true)] 16 | param( 17 | [Parameter()] 18 | [ValidateNotNullOrEmpty()] 19 | [string] 20 | $Path = "$($env:ProgramFiles)\WindowsPowerShell\Modules", 21 | [Parameter(Mandatory)] 22 | [ValidateNotNullOrEmpty()] 23 | [string] 24 | $ModuleName, 25 | [Parameter( 26 | Mandatory, 27 | ValueFromPipeline 28 | )] 29 | [ValidateNotNullOrEmpty()] 30 | [string] 31 | $ResourceName, 32 | [string] 33 | $Author = $env:USERNAME, 34 | [string] 35 | $Company = "Unknown", 36 | $Copyright = "(c) $([DateTime]::Now.Year) $env:USERNAME. All rights reserved.", 37 | [switch] 38 | $Force 39 | ) 40 | begin 41 | { 42 | $admin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") 43 | if(-not $admin -and $Path -eq "$env:ProgramFiles\WindowsPowerShell\Modules"){ 44 | throw "Must be in Administrative context to write to $Path" 45 | } 46 | 47 | $rootModule = Join-Path $Path $ModuleName 48 | Write-Verbose "Root module path - $RootModule" 49 | 50 | $rootModulePSD = Join-Path $rootModule "$($moduleName).psd1" 51 | Write-Verbose "Root module metadata file - $rootModulePSD" 52 | 53 | $rootModulePath = Join-Path $rootModule "DSCResources" 54 | Write-Verbose "DSCResources folder path $rootModulePath" 55 | 56 | if (-not (test-path $rootModulePSD)) { 57 | if($PSCmdlet.ShouldProcess($rootModule, 'Creating a base module to host DSC Resources')) { 58 | New-Item -ItemType Directory -Path $rootModule -Force:$Force | Out-Null 59 | New-ModuleManifest -Path $rootModulePSD -ModuleVersion '1.0.0' -Author $Author -CompanyName $Company -Description "CompositeResource Main module" -Copyright $Copyright 60 | } 61 | } 62 | else { 63 | Write-Verbose "Module and manifest already exist at $rootModulePSD" 64 | } 65 | 66 | if (-not (test-path $rootModulePath) ) { 67 | if($PSCmdlet.ShouldProcess($rootModulePath, 'Creating the DSCResources directory')) { 68 | New-Item -ItemType Directory -Path $rootModulePath -Force:$Force | Out-Null 69 | } 70 | } 71 | else { 72 | Write-Verbose "DSCResources folder already exists at $rootModulePath" 73 | } 74 | } 75 | process 76 | { 77 | $resourceFolder = Join-Path $rootModulePath $ResourceName 78 | $resourcePSMName = "$($ResourceName).schema.psm1" 79 | $resourcePSM = Join-Path $resourceFolder $resourcePSMName 80 | $resourcePSD = Join-Path $resourceFolder "$($ResourceName).psd1" 81 | 82 | if($PSCmdlet.ShouldProcess($resourceFolder, "Creating new resource $ResourceName")) 83 | { 84 | New-Item -ItemType Directory -Path $resourceFolder -Force:$Force | Out-Null 85 | 86 | if ((-not (test-path $resourcePSM)) -or ($force)) { 87 | New-Item -ItemType File -Path $resourcePSM -Force:$Force | Out-Null 88 | Add-Content -Path $resourcePSM -Value "Configuration $ResourceName`r`n{`r`n}" 89 | } 90 | if ((-not (test-path $resourcePSD)) -or ($force)) { 91 | New-ModuleManifest -Path $resourcePSD -RootModule $resourcePSMName -ModuleVersion '1.0.0' -Author $Author -CompanyName $Company -Copyright $Copyright 92 | } 93 | 94 | } 95 | 96 | } 97 | 98 | } 99 | 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BIG CHANGES 2 | 3 | We have moved the DSC Resources into their own repositories under PowerShell.Org. 4 | 5 | ## Why? 6 | * To make it easier to track issues for individual resources 7 | * To allow more community members to participate in the maintence of these resources 8 | * To make it easier to automate publishing of individual resources to PowerShellGet and other sources 9 | 10 | ## How can I contribute? 11 | 12 | ### If you have a new resource you want to share, 13 | * File an issue here and let us know what the name of the project should be. 14 | * We'll create a repository and make you an contributor (if you want) to that repository. 15 | * You can then populate that repository via a pull request or we can clone an existing repository in the organization. 16 | or 17 | * You can create a gist containing the files for your DSC resource and we can move them into a repository. 18 | 19 | ### If you want to help maintain an existing resource 20 | * Start by contributing something.. a bugfix, documentation, file issues 21 | * After a contribution or two, file an issue requesting to become a maintainer for the project 22 | * We (along with the other maintainers - if any exist), can start a conversation about how you can help the project. 23 | 24 | or 25 | 26 | * Fork the resource repository 27 | * Create your feature branch (`git checkout -b my-new-feature`) 28 | * Commit your changes (`git commit -am 'Add some feature'`) 29 | * Push to the branch (`git push origin my-new-feature`) 30 | * Create new Pull Request 31 | 32 | ## What is still here? 33 | So far, the DSC Tooling. We'll probably break that out as well. The focus of the repository will be documentation, examples and filing of issues that are broader than any one resource or tool. 34 | 35 | Current Repositories 36 | * [SystemHosting](https://github.com/PowerShellOrg/SystemHosting) 37 | * SHT_AllowedServices 38 | * SHT_DNSClient 39 | * SHT_GroupResource 40 | * SHT_IscsiInitiatorTargetPortal 41 | * SHT_MPIOSetting 42 | * SHT_NetAdapter 43 | * SHT_NetAdapterAdvancedProperty 44 | * SHT_NetAdapterBinding 45 | * SHT_NetAdapterNetBios 46 | * [cWebAdministration](https://github.com/PowerShellOrg/cWebAdministration) 47 | * PSHOrg_cAppPool 48 | * PSHOrg_cWebsite 49 | * [cChoco](https://github.com/PowerShellOrg/cChoco) 50 | * cChocoInstaller 51 | * cChocoPackageInstall 52 | * [StackExchangeResources](https://github.com/PowerShellOrg/StackExchangeResources) 53 | * StackExchange_CertificateStore 54 | * StackExchange_FirewallRule 55 | * StackExchange_NetworkAdapter 56 | * StackExchange_Pagefile 57 | * StackExchange_PowerPlan 58 | * StackExchange_ScheduledTask 59 | * StackExchange_SetExecutionPolicy 60 | * StackExchange_Timezone 61 | * [rchaganti](https://github.com/PowerShellOrg/rchaganti) 62 | * HostsFile 63 | * [PowerShellAccessControl](https://github.com/PowerShellOrg/PowerShellAccessControl) 64 | * PowerShellAccessControl_cAccessControlEntry 65 | * PowerShellAccessControl_cSecurityDescriptor 66 | * PowerShellAccessControl_cSecurityDescriptorSddl 67 | * [cSystemCenterManagement](https://github.com/PowerShellOrg/cSystemCenterManagement) 68 | * ICG_SCOMAgentMgmtGroup 69 | * ICG_SCOMBulkMP 70 | * ICG_SCOMImportMP 71 | * [cSqlPs](https://github.com/PowerShellOrg/cSqlPs) 72 | * PSHOrg_cSqlHAEndPoint 73 | * PSHOrg_cSqlHAGroup 74 | * PSHOrg_cSqlHAService 75 | * PSHOrg_cSqlServerInstall 76 | * PSHOrg_cWaitForSqlHAGroup 77 | * cScriptResource 78 | * [cSmbShare](https://github.com/PowerShellOrg/cSmbShare) 79 | * PSHOrg_cSmbShare 80 | * [cRDPEnabled](https://github.com/PowerShellOrg/cRDPEnabled) 81 | * PSHOrg_cRDPEnabled 82 | * [Craig-Martin](https://github.com/PowerShellOrg/Craig-Martin) 83 | * GlobalAssemblyCache 84 | * [cPSGet](https://github.com/PowerShellOrg/cPSGet) 85 | * PSHOrg_cPSGet 86 | * [cPSDesiredStateConfiguration](https://github.com/PowerShellOrg/cPSDesiredStateConfiguration) 87 | * PSHOrg_cDSCWebService 88 | * [cNetworking](https://github.com/PowerShellOrg/cNetworking) 89 | * PSHOrg_cDNSServerAddress 90 | * PSHOrg_cFirewall 91 | * PSHOrg_cIPAddress 92 | * [cHyper-V](https://github.com/PowerShellOrg/cHyper-V) 93 | * PSHOrg_cVHD 94 | * PSHOrg_cVMHost 95 | * PSHOrg_cVMHyperV 96 | * PSHOrg_cVMSwitch 97 | * PSHOrg_cVhdFileDirectory 98 | * [cFailoverCluster](https://github.com/PowerShellOrg/cFailoverCluster) 99 | * PSHOrg_cCluster 100 | * PSHOrg_cWaitForCluster 101 | * [cComputerManagement](https://github.com/PowerShellOrg/cComputerManagement) 102 | * PSHOrg_cComputer 103 | * [cActiveDirectory](https://github.com/PowerShellOrg/cActiveDirectory) 104 | * PSHOrg_cADDomain 105 | * PSHOrg_cADDomainController 106 | * PSHOrg_cADUser 107 | * PSHOrg_cWaitForADDomain 108 | -------------------------------------------------------------------------------- /Tooling/dscbuild/DscBuild.psm1: -------------------------------------------------------------------------------- 1 | . $psscriptroot\Assert-DestinationDirectory.ps1 2 | . $psscriptroot\Clear-CachedDscResource.ps1 3 | . $psscriptroot\Clear-InstalledDscResource.ps1 4 | . $psscriptroot\Compress-DscResourceModule.ps1 5 | . $psscriptroot\Copy-CurrentDscResource.ps1 6 | . $psscriptroot\DscResourceWmiClass.ps1 7 | . $psscriptroot\Invoke-DscBuild.ps1 8 | . $psscriptroot\Invoke-DscConfiguration.ps1 9 | . $psscriptroot\Invoke-DscResourceUnitTest.ps1 10 | . $psscriptroot\New-DscChecksumFile.ps1 11 | . $psscriptroot\New-DscZipFile.ps1 12 | . $psscriptroot\Publish-DscConfiguration.ps1 13 | . $psscriptroot\Publish-DscResourceModule.ps1 14 | . $psscriptroot\Publish-DscToolModule.ps1 15 | . $psscriptroot\Test-DscResourceIsValid.ps1 16 | . $psscriptroot\Where-DscResource.ps1 17 | . $psscriptroot\Resolve-ConfigurationProperty.ps1 18 | . $psscriptroot\Get-DscResourceVersion.ps1 19 | . $psscriptroot\Update-ModuleMetadataVersion.ps1 20 | 21 | 22 | $DscBuildParameters = $null 23 | 24 | function Add-DscBuildParameter { 25 | <# 26 | .Synopsis 27 | Adds a parameter to the module scoped DscBuildParameters object. 28 | .Description 29 | Adds a parameter to the module scoped DscBuildParameters object. This object is available to all functions in a build and is built from the parameters to Invoke-DscBuild. 30 | .Example 31 | Add-DscBuildParameter -Name ProgramFilesModuleDirectory -value (join-path $env:ProgramFiles 'WindowsPowerShell\Modules') 32 | #> 33 | [cmdletbinding(SupportsShouldProcess=$true)] 34 | param ( 35 | #Name of the property to add 36 | [string] 37 | $Name, 38 | #Value of the property to add 39 | [object] 40 | $Value 41 | ) 42 | 43 | if ($psboundparameters.containskey('WhatIf')) { 44 | $psboundparameters.Remove('WhatIf') | out-null 45 | } 46 | 47 | Write-Verbose '' 48 | Write-Verbose "Adding DscBuildParameter: $Name" 49 | Write-Verbose "`tWith Value: $Value" 50 | $script:DscBuildParameters | 51 | add-member -membertype Noteproperty -force @psboundparameters 52 | Write-Verbose '' 53 | } 54 | 55 | function Test-BuildResource { 56 | <# 57 | .Synopsis 58 | Checks to see if a build started with Invoke-DscBuild will be processing new or existing resources. 59 | .Description 60 | Checks to see if a build started with Invoke-DscBuild will be processing new or existing resources. This is used by functions in the module to determine whether a particular block needs to execute. 61 | .Example 62 | if (Test-BuildResource) { do something...} 63 | #> 64 | [cmdletbinding()] 65 | param () 66 | $IsBuild = ( $script:DscBuildParameters.Resource -or 67 | (-not ($script:DscBuildParameters.Tools -or $script:DscBuildParameters.Configuration) ) ) 68 | Write-Verbose '' 69 | Write-Verbose "Is a Resource Build - $IsBuild" 70 | Write-Verbose '' 71 | return $IsBuild 72 | } 73 | 74 | function Test-BuildConfiguration { 75 | <# 76 | .Synopsis 77 | Checks to see if a build started with Invoke-DscBuild will be processing new or existing configurations. 78 | .Description 79 | Checks to see if a build started with Invoke-DscBuild will be processing new or existing configurations. This is used by functions in the module to determine whether a particular block needs to execute. 80 | .Example 81 | if (Test-BuildConfiguration) { do something...} 82 | #> 83 | [cmdletbinding()] 84 | param () 85 | $IsBuild = ( $script:DscBuildParameters.Configuration -or 86 | (-not ($script:DscBuildParameters.Tools -or $script:DscBuildParameters.Resource) ) ) 87 | Write-Verbose '' 88 | Write-Verbose "Is a Configuration Build - $IsBuild" 89 | Write-Verbose '' 90 | return $IsBuild 91 | } 92 | 93 | function Test-BuildTools { 94 | <# 95 | .Synopsis 96 | Checks to see if a build started with Invoke-DscBuild will be processing new or existing tools. 97 | .Description 98 | Checks to see if a build started with Invoke-DscBuild will be processing new or existing tools. This is used by functions in the module to determine whether a particular block needs to execute. 99 | .Example 100 | if (Test-BuildTools) { do something...} 101 | #> 102 | [cmdletbinding()] 103 | param () 104 | $IsBuild = ( $script:DscBuildParameters.Tools -or 105 | (-not ($script:DscBuildParameters.Configuration -or $script:DscBuildParameters.Resource) ) ) 106 | Write-Verbose '' 107 | Write-Verbose "Is a Tools Build - $IsBuild" 108 | Write-Verbose '' 109 | return $IsBuild 110 | } 111 | -------------------------------------------------------------------------------- /Tooling/cDscDiagnostics/cDscDiagnosticsFormat.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Microsoft.PowerShell.cDscDiagnostics.GroupedEvents 7 | 8 | Microsoft.PowerShell.cDscDiagnostics.GroupedEvents 9 | 10 | 11 | 12 | 13 | 14 | 14 15 | 16 | 17 | 18 | 10 19 | 20 | 21 | 22 | 21 23 | 24 | 25 | 26 | 8 27 | 28 | 29 | 30 | 37 31 | 32 | 33 | 34 | 21 35 | 36 | 37 | 38 | 39 | 40 | 41 | ComputerName 42 | 43 | 44 | SequenceId 45 | 46 | 47 | TimeCreated 48 | 49 | 50 | Result 51 | 52 | 53 | JobID 54 | 55 | 56 | AllEvents 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | Microsoft.PowerShell.cDscDiagnostics.TraceOutput 65 | 66 | Microsoft.PowerShell.cDscDiagnostics.TraceOutput 67 | 68 | 69 | 70 | 71 | 72 | 14 73 | 74 | 75 | 76 | 12 77 | 78 | 79 | 80 | 21 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | ComputerName 91 | 92 | 93 | EventType 94 | 95 | 96 | TimeCreated 97 | 98 | 99 | Message 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Tooling/DscDevelopment/Test-DscBuild.ps1: -------------------------------------------------------------------------------- 1 | function Test-DscBuild 2 | { 3 | <# 4 | .Synopsis 5 | Runs a build of the Stack Exchange DSC configurations. 6 | .DESCRIPTION 7 | Creates an isolated runspace and executes a build, returning input immediately to the console window. 8 | This is the same process used by the build server to publish the current DSC configurations. 9 | 10 | WARNING: This will replace the current DSC resource modules on the machine running the build. 11 | .EXAMPLE 12 | Test-DscBuild -Development 13 | Runs a build to the Dsc-Dev folder that should be located in the working directory. 14 | .EXAMPLE 15 | Test-DscBuild -DeploymentDirectory c:\temp 16 | Runs a build to the c:\temp folder and does not create compressed versions of the resource modules 17 | (saves a bit of time when repeatedly iterating on configuration changes) 18 | #> 19 | [cmdletbinding(supportsshouldprocess=$true)] 20 | param ( 21 | #The path to local copy of the DSC-Prod repository 22 | #Defaults to two folders above the DscDevelopment module (..\..\DscDevelopment) 23 | [parameter( 24 | Position = 0 25 | )] 26 | [string] 27 | $WorkingDirectory = (Split-Path (Split-Path $psscriptroot)), 28 | 29 | #The path to the script build script to run. 30 | #By default, this points to the TeamCityBuild folder in the working directory. 31 | [parameter( 32 | Position = 1 33 | )] 34 | [ValidateNotNullOrEmpty()] 35 | [string] 36 | $BuildScript, 37 | 38 | #Destination for the generated configurations and packaged resources. 39 | #Two folders will be created underneath this location, one for Configurations and one for 40 | #resource modules. 41 | [parameter( 42 | Mandatory, 43 | Position = 2 44 | )] 45 | [ValidateNotNullOrEmpty()] 46 | [string] 47 | $DestinationRootDirectory, 48 | 49 | [parameter()] 50 | [ValidateNotNullOrEmpty()] 51 | [string] 52 | $DestinationToolDirectory, 53 | 54 | [parameter()] 55 | [string] 56 | $CurrentToolsDirectory, 57 | 58 | # 59 | [parameter()] 60 | [switch] 61 | $ConfigurationOnly, 62 | 63 | [parameter()] 64 | [switch] 65 | $ResourceOnly, 66 | 67 | [parameter()] 68 | [switch] 69 | $ToolsOnly, 70 | 71 | [switch] 72 | $CleanEnvironment, 73 | 74 | [switch] 75 | $ShowConfigurationDebugMessages 76 | ) 77 | 78 | $PassedParameters = @{ 79 | WorkingDirectory = $WorkingDirectory 80 | DestinationToolDirectory = $DestinationToolDirectory 81 | CurrentToolsDirectory = $CurrentToolsDirectory 82 | ConfigurationOnly = $ConfigurationOnly 83 | ResourceOnly = $ResourceOnly 84 | ToolsOnly = $ToolsOnly 85 | ShowConfigurationDebugMessages = $ShowConfigurationDebugMessages 86 | } 87 | 88 | foreach ($key in ('Whatif', 'Verbose', 'Debug')) 89 | { 90 | if ($PSBoundParameters.ContainsKey($key)) { 91 | $PassedParameters[$key] = $PSBoundParameters[$key] 92 | } 93 | } 94 | 95 | if (-not (Test-Path $WorkingDirectory)) 96 | { 97 | throw 'Working directory not found. Please supply a path to the directory with DSC_Resources and DSC_Configuration repositories.' 98 | } 99 | 100 | if ($PSBoundParameters.ContainsKey('DestinationRootDirectory')) 101 | { 102 | $PassedParameters.DestinationRootDirectory = $DestinationRootDirectory 103 | } 104 | else 105 | { 106 | throw "Need to supply a DestinationRootDirectory." 107 | } 108 | 109 | Write-Verbose "Parameters to pass are: " 110 | foreach ($key in $PassedParameters.Keys) 111 | { 112 | Write-Verbose "`t`tKey: $Key Value: $($PassedParameters[$key])" 113 | } 114 | 115 | if (-not (Test-Path $BuildScript)) 116 | { 117 | throw @" 118 | Failed to find a build script at $BuildScript. 119 | Either specify a path to the build script or clone the TeamCityBuild repository adjacent to the DSC-Prod repo. 120 | "@ 121 | } 122 | 123 | if ($CleanEnvironment) { 124 | remove-item (join-path $PassedParameters.DestinationDirectory 'Configuration') -recurse -erroraction SilentlyContinue 125 | remove-item (join-path $PassedParameters.DestinationDirectory 'Modules') -recurse -erroraction SilentlyContinue 126 | remove-item (join-path $PassedParameters.WorkingDirectory 'BuildOutput') -recurse -erroraction SilentlyContinue 127 | } 128 | 129 | start-job -ArgumentList $BuildScript, $PassedParameters { 130 | param ([string]$BuildScript, [System.Collections.Hashtable]$PassedParameters) 131 | . $BuildScript @PassedParameters 132 | } | receive-job -wait 133 | 134 | } 135 | 136 | 137 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Invoke-DscBuild.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-DscBuild 2 | { 3 | <# 4 | .Synopsis 5 | Starts a build of DSC configurations, resources, and tools. 6 | .Description 7 | Starts a build of DSC configurations, resources, and tools. This command is the global entry point for DSC builds controls the flow of operations. 8 | .Example 9 | $BuildParameters = @{ 10 | WorkingDirectory = 'd:\gitlab\' 11 | DestinationRootDirectory = 'd:\PullServerOutputTest\' 12 | DestinationToolDirectory = 'd:\ToolsOutputTest\' 13 | } 14 | Invoke-DscBuild @BuildParameters 15 | #> 16 | [cmdletbinding(SupportsShouldProcess=$true)] 17 | param ( 18 | #Root of your source control check outs or the folder above your Dsc_Configuration, Dsc_Resources, and Dsc_Tools directory. 19 | [parameter(mandatory)] 20 | [string] 21 | $WorkingDirectory, 22 | 23 | #Directory containing all the resources to process. Defaults to a Dsc_Resources directory under the working directory. 24 | [parameter()] 25 | [ValidateNotNullOrEmpty()] 26 | [string] 27 | $SourceResourceDirectory, 28 | 29 | #Directory containing all the tools to process. Defaults to a Dsc_Tooling directory under the working directory. 30 | [parameter()] 31 | [ValidateNotNullOrEmpty()] 32 | [string] 33 | $SourceToolDirectory, 34 | 35 | #Root of the location where pull server artificates (configurations and zipped resources) are published. 36 | [parameter(mandatory)] 37 | [string] 38 | $DestinationRootDirectory, 39 | 40 | #Destination for any tools that are published. 41 | [parameter(mandatory)] 42 | [string] 43 | $DestinationToolDirectory, 44 | 45 | #Modules to exclude from the resource testing and deployment process. 46 | [parameter(mandatory)] 47 | [string[]] 48 | $ExcludedModules, 49 | 50 | #The configuration data hashtable for the configuration to apply against. 51 | [parameter(mandatory)] 52 | [System.Collections.Hashtable] 53 | $ConfigurationData, 54 | 55 | #The name of the module to load that contains the configuration to run. 56 | [parameter(mandatory)] 57 | [string] 58 | $ConfigurationModuleName, 59 | 60 | #The name of the configuration to run. 61 | [parameter(mandatory)] 62 | [string] 63 | $ConfigurationName, 64 | 65 | #Custom location for the location of the DSC Build Tools modules. 66 | [parameter()] 67 | [ValidateNotNullOrEmpty()] 68 | [string] 69 | $CurrentToolsDirectory, 70 | 71 | #This switch is used to indicate that configuration documents should be generated and deployed. 72 | [parameter()] 73 | [switch] 74 | $Configuration, 75 | 76 | #This switch is used to indicate that custom resources should be tested and deployed. 77 | [parameter()] 78 | [switch] 79 | $Resource, 80 | 81 | #This switch is used to indicate that the custom tools should be tested and deployed. 82 | [parameter()] 83 | [switch] 84 | $Tools 85 | 86 | ) 87 | 88 | $script:DscBuildParameters = new-object PSObject -property $PSBoundParameters 89 | if (-not $PSBoundParameters.ContainsKey('SourceResourceDirectory')) { 90 | Add-DscBuildParameter -Name SourceResourceDirectory -value (Join-Path $WorkingDirectory 'Dsc_Resources') 91 | } 92 | if (-not $PSBoundParameters.ContainsKey('SourceToolDirectory')) { 93 | Add-DscBuildParameter -Name SourceToolDirectory -value (Join-Path $WorkingDirectory 'Dsc_Tooling') 94 | } 95 | if (-not $PSBoundParameters.ContainsKey('CurrentToolsDirectory')) { 96 | Add-DscBuildParameter -Name CurrentToolsDirectory -value (join-path $env:ProgramFiles 'WindowsPowerShell\Modules') 97 | } 98 | Add-DscBuildParameter -Name ProgramFilesModuleDirectory -value (join-path $env:ProgramFiles 'WindowsPowerShell\Modules') 99 | 100 | $ParametersToPass = @{} 101 | foreach ($key in ('Whatif', 'Verbose', 'Debug')) 102 | { 103 | if ($PSBoundParameters.ContainsKey($key)) { 104 | $ParametersToPass[$key] = $PSBoundParameters[$key] 105 | } 106 | } 107 | 108 | Clear-InstalledDscResource @ParametersToPass 109 | Clear-CachedDscResource @ParametersToPass 110 | 111 | Invoke-DscResourceUnitTest @ParametersToPass 112 | 113 | Copy-CurrentDscResource @ParametersToPass 114 | Copy-CurrentDscTools @ParametersToPass 115 | 116 | Test-DscResourceIsValid @ParametersToPass 117 | 118 | Assert-DestinationDirectory @ParametersToPass 119 | 120 | Invoke-DscConfiguration @ParametersToPass 121 | 122 | Compress-DscResourceModule @ParametersToPass 123 | Publish-DscToolModule @ParametersToPass 124 | Publish-DscResourceModule @ParametersToPass 125 | Publish-DscConfiguration @ParametersToPass 126 | } -------------------------------------------------------------------------------- /Tooling/DscConfiguration/New-DscNodeMetadata.ps1: -------------------------------------------------------------------------------- 1 | function New-DscNodeMetadata 2 | { 3 | <# 4 | .Synopsis 5 | Creates a new Dsc metadata file describing a node. 6 | .Description 7 | Create a new Dsc metadata file to populate AllNodes in a Dsc Configuration. 8 | .Example 9 | New-DscNodeMetadata -Name NY-TestSQL01 -Location NY -ServerType VM 10 | .Example 11 | New-DscNodeMetadata -Name NY-TestService01 -Location NY -ServerType Physical 12 | #> 13 | param 14 | ( 15 | #Server name, same as ActiveDirectory server account name. 16 | [parameter( 17 | Mandatory, 18 | ValueFromPipelineByPropertyName, 19 | Position = 1 20 | )] 21 | [string] 22 | $Name, 23 | 24 | #Data center or site the server is in. 25 | [parameter( 26 | Mandatory, 27 | ValueFromPipelineByPropertyName, 28 | Position = 1 29 | )] 30 | [string] 31 | $Location, 32 | 33 | #Type of server (physical or virtual) 34 | [parameter( 35 | Mandatory, 36 | ValueFromPipelineByPropertyName 37 | )] 38 | [ValidateSet('Physical','VM')] 39 | [string] 40 | $ServerType, 41 | 42 | 43 | #Unique identifier for this node. Will automatically generate one if not supplied. 44 | [parameter( 45 | ValueFromPipelineByPropertyName 46 | )] 47 | [ValidateNotNullOrEmpty()] 48 | [guid] 49 | $NodeName, 50 | 51 | #Path to the AllNodes subfolder in the configuration data folder. 52 | #Defaults to ${repository root}/Configuration/AllNodes 53 | [parameter()] 54 | [ValidateNotNullOrEmpty()] 55 | [string] 56 | $Path 57 | ) 58 | begin 59 | { 60 | if ($psboundparameters.containskey('path')) { 61 | $psboundparameters.Remove('path') | out-null 62 | } 63 | Resolve-ConfigurationDataPath -Path $Path 64 | 65 | $AllNodesConfigurationPath = (join-path $script:ConfigurationDataPath 'AllNodes') 66 | } 67 | process 68 | { 69 | if (-not $psboundparameters.containskey('NodeName')){ 70 | $psboundparameters.Add('NodeName', [guid]::NewGuid().Guid) 71 | } 72 | Out-ConfigurationDataFile -Parameters $psboundparameters -ConfigurationDataPath $AllNodesConfigurationPath 73 | } 74 | 75 | } 76 | 77 | function New-DscServiceMetadata { 78 | [cmdletbinding()] 79 | param ( 80 | [parameter( 81 | Mandatory, 82 | ValueFromPipelineByPropertyName 83 | )] 84 | [string] 85 | $Name, 86 | [string[]] 87 | $Nodes, 88 | [string[]] 89 | $Roles, 90 | [string] 91 | $Path 92 | ) 93 | 94 | begin { 95 | if ($psboundparameters.containskey('path')) { 96 | $psboundparameters.Remove('path') | out-null 97 | } 98 | Resolve-ConfigurationDataPath -Path $Path 99 | 100 | $ServicesConfigurationPath = (join-path $script:ConfigurationDataPath 'Services') 101 | } 102 | process { 103 | $OutConfigurationDataFileParams = @{ 104 | Parameters = $psboundparameters 105 | ConfigurationDataPath = $ServicesConfigurationPath 106 | DoNotIncludeName = $true 107 | } 108 | Out-ConfigurationDataFile @OutConfigurationDataFileParams 109 | } 110 | } 111 | 112 | function New-DscSiteMetadata { 113 | [cmdletbinding()] 114 | param ( 115 | [string] 116 | $Name, 117 | [string] 118 | $Path 119 | ) 120 | 121 | begin { 122 | if ($psboundparameters.containskey('path')) { 123 | $psboundparameters.Remove('path') | out-null 124 | } 125 | Resolve-ConfigurationDataPath -Path $Path 126 | 127 | $SiteDataConfigurationPath = (join-path $script:ConfigurationDataPath 'SiteData') 128 | } 129 | process { 130 | Out-ConfigurationDataFile -Parameters $psboundparameters -ConfigurationDataPath $SiteDataConfigurationPath 131 | } 132 | } 133 | 134 | 135 | function Out-ConfigurationDataFile { 136 | [cmdletbinding()] 137 | param($Parameters, $ConfigurationDataPath, [switch]$DoNotIncludeName) 138 | 139 | $StartingBlock = "@{`r`n" 140 | $EndingBlock = "`r`n}" 141 | $ExcludedParameters = [System.Management.Automation.Internal.CommonParameters].GetProperties().Name 142 | if ($DoNotIncludeName) { 143 | $ExcludedParameters += 'Name' 144 | } 145 | $ofs = "', '" 146 | 147 | $configuration = $StartingBlock 148 | foreach ($key in $Parameters.keys) { 149 | if ($ExcludedParameters -notcontains $key ) 150 | { 151 | $Configuration += "`r`n`t$key = '$($Parameters[$key])'" 152 | } 153 | } 154 | 155 | $Configuration += $EndingBlock 156 | 157 | $configuration | out-file (join-path $ConfigurationDataPath "$($Parameters['Name'].toupper()).psd1") -Encoding Ascii 158 | 159 | } 160 | 161 | -------------------------------------------------------------------------------- /Tooling/DscOperations/Set-DscClient.ps1: -------------------------------------------------------------------------------- 1 | function Set-DscClient 2 | { 3 | <# 4 | .Synopsis 5 | Configures the Local Configuration Manager (LCM) for a server 6 | .Description 7 | Configures the Local Configuration Manager (LCM) for a server. Parameters can either be specified manually or looked up from configuration data. 8 | .Example 9 | Set-DscClient -Name OR-WEB01 -ConfigurationData (Get-DscConfigurationData -path d:\gitlab\Dsc_Configuration) 10 | #> 11 | [cmdletbinding(DefaultParameterSetName='FromCommandLine')] 12 | param ( 13 | #Name of the host to configure. If you are using -ConfigurationData, this will need to be the host name as specified in the Name property. 14 | [parameter( 15 | Position = 0, 16 | Mandatory, 17 | ValueFromPipelineByPropertyName 18 | )] 19 | [ValidateNotNullOrEmpty()] 20 | [alias('ComputerName', 'PSComputerName', '__Server')] 21 | [string] 22 | $Name, 23 | 24 | #NodeName is what will be set for the ConfigurationID on the LCM. 25 | [parameter( 26 | Position = 1, 27 | ValueFromPipelineByPropertyName, 28 | Mandatory, 29 | ParameterSetName = 'FromCommandLine' 30 | )] 31 | [string] 32 | $NodeName, 33 | 34 | #The url for the Pull Server to contact for configurations. 35 | [parameter( 36 | Position = 2, 37 | ValueFromPipelineByPropertyName, 38 | Mandatory, 39 | ParameterSetName = 'FromCommandLine' 40 | )] 41 | [string] 42 | $PullServerUrl, 43 | 44 | #The thumbprint for the certificate the LCM should use to decrypt passed credentials. 45 | [parameter( 46 | Position = 3, 47 | ValueFromPipelineByPropertyName, 48 | Mandatory, 49 | ParameterSetName = 'FromCommandLine' 50 | )] 51 | [string] 52 | $CertificateThumbprint, 53 | 54 | #Setting this flag will just clear the existing LCM settings. 55 | [parameter( 56 | ParameterSetName = 'ClearConfigOnly' 57 | )] 58 | [switch] 59 | $ClearConfigurationOnly, 60 | 61 | #The ConfigurationData hashtable (from Get-DscConfigurationData in the DscConfiguration module). 62 | [parameter( 63 | Position = 1, 64 | Mandatory, 65 | ParameterSetName = 'FromConfigurationData' 66 | )] 67 | [System.Collections.Hashtable] 68 | $ConfigurationData 69 | ) 70 | 71 | process { 72 | 73 | if ($PSCmdlet.ParameterSetName -eq 'FromConfigurationData') { 74 | $Node = ($ConfigurationData.AllNodes | 75 | Where-Object { $_.Name -like $Name }) 76 | $NodeName = $Node.NodeName 77 | $CertificateThumbprint = $ConfigurationData['AllNodes'].where({$_.NodeName -eq '*'}).CertificateID 78 | $PullServerUrl = Resolve-DscConfigurationProperty -ConfigurationData $ConfigurationData -Node $Node -PropertyName 'PullServer' 79 | } 80 | 81 | $ICMParams = @{ 82 | Session = New-PSSession -ComputerName $Name 83 | } 84 | 85 | Write-Verbose 'Clearing out pending and current MOFs and existing LCM configuration.' 86 | icm @ICMParams -ScriptBlock { 87 | dir 'c:\windows\System32\configuration\*.mof*' | Remove-Item 88 | Get-Process -Name WmiPrvSE -ErrorAction SilentlyContinue | 89 | Stop-Process -Force 90 | } 91 | 92 | if (-not $ClearConfigurationOnly) 93 | { 94 | Write-Verbose "" 95 | Write-Verbose "$Name will be configured with: " 96 | Write-Verbose "`tNodeName = $NodeName" 97 | Write-Verbose "`tConfigurationID = $ConfigurationID" 98 | Write-Verbose "`tPullServerUrl = $PullServerUrl" 99 | Write-Verbose "`tCertificateID = $CertificateThumbprint" 100 | 101 | 102 | configuration PullClientConfig 103 | { 104 | param ($NodeName, $ConfigurationID, $PullServer, $LocalCertificateThumbprint) 105 | 106 | Node $NodeName 107 | { 108 | LocalConfigurationManager 109 | { 110 | AllowModuleOverwrite = 'True' 111 | CertificateID = $LocalCertificateThumbprint 112 | ConfigurationID = $ConfigurationID 113 | ConfigurationModeFrequencyMins = 60 114 | ConfigurationMode = 'ApplyAndAutoCorrect' 115 | RebootNodeIfNeeded = 'True' 116 | RefreshMode = 'PULL' 117 | DownloadManagerName = 'WebDownloadManager' 118 | DownloadManagerCustomData = (@{ServerUrl = "http://$PullServer/psdscpullserver.svc";AllowUnsecureConnection = 'True'}) 119 | } 120 | } 121 | } 122 | 123 | if (-not [string]::IsNullOrEmpty($NodeName)) 124 | { 125 | Write-Verbose "Generating Pull Client Configuration for $Name." 126 | PullClientConfig -NodeName $Name -ConfigurationID $NodeName -PullServer $PullServerUrl -LocalCertificateThumbprint $CertificateThumbprint 127 | 128 | Write-Verbose "Applying Pull Client Configuration for $Name" 129 | Set-DSCLocalConfigurationManager -Path .\PullClientConfig -ComputerName $Name -Verbose 130 | Remove-Item ./pullclientconfig -Recurse -Force 131 | } 132 | else 133 | { 134 | Write-Verbose "No matching NodeName for $Name." 135 | } 136 | 137 | } 138 | } 139 | } 140 | 141 | 142 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/ConvertTo-EncryptedFile.ps1: -------------------------------------------------------------------------------- 1 | Function ConvertTo-EncryptedFile 2 | { 3 | [cmdletbinding(DefaultParameterSetName='LocalCertStoreAndFilePath')] 4 | Param( 5 | #Path to the file to encrypt. 6 | [Parameter( 7 | Position=0, 8 | Mandatory=$true, 9 | ValueFromPipelineByPropertyName=$true, 10 | ParameterSetName = 'LocalCertStoreAndFilePath' 11 | )] 12 | [Parameter( 13 | Position=0, 14 | Mandatory=$true, 15 | ValueFromPipelineByPropertyName=$true, 16 | ParameterSetName = 'ArbitraryCertAndFilePath' 17 | )] 18 | [string] 19 | $Path, 20 | 21 | #FileInfo object to encrypt. 22 | [Parameter( 23 | Position=0, 24 | ValueFromPipeline=$true, 25 | ParameterSetName = 'LocalCertStoreAndInputObject' 26 | )] 27 | [Parameter( 28 | Position=0, 29 | ValueFromPipeline=$true, 30 | ParameterSetName = 'ArbitraryCertAndInputObject' 31 | )] 32 | [System.IO.FileInfo] 33 | $InputObject, 34 | 35 | #Can be a path to the local cert store like Cert:\CurrentUser\My\9554F368FEA619A655A1D49408FC13C3E0D60E11 36 | [Parameter( 37 | mandatory=$true, 38 | position = 1, 39 | ParameterSetName = 'LocalCertStoreAndFilePath' 40 | )] 41 | [Parameter( 42 | mandatory=$true, 43 | position = 1, 44 | ParameterSetName = 'LocalCertStoreAndInputObject' 45 | )] 46 | [string] 47 | $CertificatePath, 48 | 49 | #Must be a System.Security.Cryptography.X509Certificates.X509Certificate2 object 50 | [Parameter( 51 | mandatory=$true, 52 | position = 1, 53 | ParameterSetName = 'ArbitraryCertAndInputObject' 54 | )] 55 | [Parameter( 56 | mandatory=$true, 57 | position = 1, 58 | ParameterSetName = 'ArbitraryCertAndFilePath' 59 | )] 60 | [System.Security.Cryptography.X509Certificates.X509Certificate2] 61 | $Certificate, 62 | [parameter()] 63 | [string] 64 | $FileExtension = 'encrypted' 65 | ) 66 | 67 | process 68 | { 69 | switch ($PSCmdlet.ParameterSetName) 70 | { 71 | 'LocalCertStoreAndFilePath' { Write-Verbose "Loading certificate from $CertificatePath"; $Certificate = Get-Item $CertificatePath } 72 | 'LocalCertStoreAndInputObject' { Write-Verbose "Loading certificate from $CertificatePath"; $Certificate = Get-Item $CertificatePath ; $Path = $InputObject.FullName } 73 | 'ArbitraryCertAndInputObject' { $Path = $InputObject.FullName } 74 | 'ArbitraryCertAndFilePath' { } 75 | } 76 | try 77 | { 78 | $Path = (Resolve-Path $Path -ErrorAction Stop).ProviderPath 79 | 80 | $AesProvider = New-Object System.Security.Cryptography.AesManaged 81 | $AesProvider.KeySize = 256 82 | $AesProvider.BlockSize = 128 83 | $AesProvider.Mode = [System.Security.Cryptography.CipherMode]::CBC 84 | 85 | $KeyFormatter = New-Object System.Security.Cryptography.RSAPKCS1KeyExchangeFormatter($Certificate.PublicKey.Key) 86 | [Byte[]]$KeyEncrypted = $KeyFormatter.CreateKeyExchange($AesProvider.Key, $AesProvider.GetType()) 87 | [Byte[]]$LenKey = $Null 88 | [Byte[]]$LenIV = $Null 89 | [Int]$LKey = $KeyEncrypted.Length 90 | $LenKey = [System.BitConverter]::GetBytes($LKey) 91 | [Int]$LIV = $AesProvider.IV.Length 92 | $LenIV = [System.BitConverter]::GetBytes($LIV) 93 | 94 | $FileStreamWriter = $Null 95 | Try 96 | { 97 | $FileStreamWriter = New-Object System.IO.FileStream("$Path.$FileExtension", [System.IO.FileMode]::Create) 98 | } 99 | Catch 100 | { 101 | $message = "Unable to open output file ($Path.$FileExtension) for writing." 102 | throw $message 103 | } 104 | 105 | $FileStreamWriter.Write($LenKey, 0, 4) 106 | $FileStreamWriter.Write($LenIV, 0, 4) 107 | $FileStreamWriter.Write($KeyEncrypted, 0, $LKey) 108 | $FileStreamWriter.Write($AesProvider.IV, 0, $LIV) 109 | 110 | $Transform = $AesProvider.CreateEncryptor() 111 | $CryptoStream = New-Object System.Security.Cryptography.CryptoStream($FileStreamWriter, $Transform, [System.Security.Cryptography.CryptoStreamMode]::Write) 112 | [Int]$Count = 0 113 | [Int]$Offset = 0 114 | [Int]$BlockSizeBytes = $AesProvider.BlockSize / 8 115 | 116 | [Byte[]]$Data = New-Object Byte[] $BlockSizeBytes 117 | [Int]$BytesRead = 0 118 | 119 | Try 120 | { 121 | $FileStreamReader = New-Object System.IO.FileStream("$Path", [System.IO.FileMode]::Open) 122 | } 123 | Catch 124 | { 125 | throw "Unable to open input file ($Path) for reading." 126 | } 127 | 128 | Do 129 | { 130 | $Count = $FileStreamReader.Read($Data, 0, $BlockSizeBytes) 131 | $Offset += $Count 132 | $CryptoStream.Write($Data, 0, $Count) 133 | $BytesRead += $BlockSizeBytes 134 | } While ($Count -gt 0) 135 | 136 | $CryptoStream.FlushFinalBlock() 137 | } 138 | catch 139 | { 140 | throw $_ 141 | } 142 | finally 143 | { 144 | $CryptoStream.Close() 145 | $FileStreamReader.Close() 146 | $FileStreamWriter.Close() 147 | } 148 | } 149 | } 150 | 151 | 152 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Update-ModuleMetadataVersion.ps1: -------------------------------------------------------------------------------- 1 | function Update-ModuleMetadataVersion 2 | { 3 | [cmdletbinding(DefaultParameterSetName='ByDirectoryInfo')] 4 | param ( 5 | [parameter( 6 | ParameterSetName = 'ByPath', 7 | Position = 0, 8 | Mandatory, 9 | ValueFromPipelineByPropertyName 10 | )] 11 | [string] 12 | $Path, 13 | [parameter( 14 | ParameterSetName = 'ByDirectoryInfo', 15 | Mandatory, 16 | ValueFromPipeline 17 | )] 18 | [System.IO.DirectoryInfo] 19 | $InputObject 20 | ) 21 | process 22 | { 23 | $ModuleMetadataFile = '' 24 | switch ($PSCmdlet.ParameterSetName) 25 | { 26 | 'ByPath' { $ModuleMetadataFile = Resolve-ModuleMetadataFile -path $Path } 27 | 'ByDirectoryInfo' { $ModuleMetadataFile = Resolve-ModuleMetadataFile -InputObject $InputObject } 28 | } 29 | 30 | if (-not [string]::IsNullOrEmpty($ModuleMetadataFile)) 31 | { 32 | Write-Verbose "Loading PSD1 properties." 33 | $ModuleMetadataHash = Get-Hashtable -Path $ModuleMetadataFile 34 | $Version = [Version]::Parse( $ModuleMetadataHash.ModuleVersion ) 35 | Write-Verbose "Current build is $Version" 36 | 37 | $NewVersion = "$($Version.Major).$($Version.Minor).$($Version.Build + 1)" 38 | Write-Verbose "New build is $NewVersion" 39 | $ModuleMetadataHash.ModuleVersion = $NewVersion 40 | 41 | Write-Verbose "Writing new manifest file - $ModuleMetadataFile." 42 | New-ModuleManifest -Path $ModuleMetadataFile @ModuleMetadataHash 43 | 44 | Get-Item (Split-Path $ModuleMetadataFile) 45 | } 46 | else 47 | { 48 | Write-Warning "No module metadata file updated." 49 | } 50 | } 51 | } 52 | 53 | function Resolve-ModuleMetadataFile 54 | { 55 | [cmdletbinding(DefaultParameterSetName='ByDirectoryInfo')] 56 | param ( 57 | [parameter( 58 | ParameterSetName = 'ByPath', 59 | Mandatory, 60 | ValueFromPipelineByPropertyName 61 | )] 62 | [string] 63 | $Path, 64 | [parameter( 65 | ParameterSetName = 'ByDirectoryInfo', 66 | Mandatory, 67 | ValueFromPipeline 68 | )] 69 | [System.IO.DirectoryInfo] 70 | $InputObject 71 | 72 | ) 73 | 74 | process 75 | { 76 | $MetadataFileFound = $true 77 | $MetadataFilePath = '' 78 | Write-Verbose "Using Parameter set - $($PSCmdlet.ParameterSetName)" 79 | switch ($PSCmdlet.ParameterSetName) 80 | { 81 | 'ByPath' { 82 | Write-Verbose "Testing Path - $path" 83 | if (Test-Path $Path) 84 | { 85 | Write-Verbose "`tFound $path." 86 | $item = (Get-Item $Path) 87 | if ($item.psiscontainer) 88 | { 89 | Write-Verbose "`t`tIt is a folder." 90 | $ModuleName = Split-Path $Path -Leaf 91 | $MetadataFilePath = Join-Path $Path "$ModuleName.psd1" 92 | $MetadataFileFound = Test-Path $MetadataFilePath 93 | } 94 | else 95 | { 96 | if ($item.Extension -like '.psd1') 97 | { 98 | Write-Verbose "`t`tIt is a module metadata file." 99 | $MetadataFilePath = $item.FullName 100 | $MetadataFileFound = $true 101 | } 102 | else 103 | { 104 | $ModulePath = Split-Path $Path 105 | Write-Verbose "`t`tSearching for module metadata folder in $ModulePath" 106 | $ModuleName = Split-Path $ModulePath -Leaf 107 | Write-Verbose "`t`tModule name is $ModuleName." 108 | $MetadataFilePath = Join-Path $ModulePath "$ModuleName.psd1" 109 | Write-Verbose "`t`tChecking for $MetadataFilePath." 110 | $MetadataFileFound = Test-Path $MetadataFilePath 111 | } 112 | } 113 | } 114 | else 115 | { 116 | $MetadataFileFound = $false 117 | } 118 | } 119 | 'ByDirectoryInfo' { 120 | $ModuleName = $InputObject.Name 121 | $MetadataFilePath = Join-Path $InputObject.FullName "$ModuleName.psd1" 122 | $MetadataFileFound = Test-Path $MetadataFilePath 123 | } 124 | 125 | } 126 | 127 | if ($MetadataFileFound -and (-not [string]::IsNullOrEmpty($MetadataFilePath))) 128 | { 129 | Write-Verbose "Found a module metadata file at $MetadataFilePath." 130 | Convert-path $MetadataFilePath 131 | } 132 | else 133 | { 134 | Write-Error "Failed to find a module metadata file at $MetadataFilePath." 135 | } 136 | } 137 | } 138 | 139 | 140 | -------------------------------------------------------------------------------- /Tooling/DscDevelopment/Deserializer.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # Functions for examining DSC MOF documents. # 3 | # Because parsing and validation require that the classes 4 | # are available when loading an instance document, we maintain and in-memory cache of the classes 5 | # that are loaded. The typical use pattern looks something like: 6 | # 7 | # Reset-CimClassCache # Clears the cache, then loads the system default DSC classes 8 | # 9 | # Add-CachedCimClass -MofClassFile myClasses.mof # add classes defined in the file to the cache 10 | # Add-CachedCimClass -MofClassFile moreOfMyClasses.mof # add classes defined in a second file 11 | # 12 | # Get-CachedCimClass -ListLoadedFiles # List all of the loaded files 13 | # Get-CachedCimClass -FileName (rvpa myClasses.mof) # List all of the classes defined in specifed file. 14 | # 15 | # Import-CimInstances -MofInstanceFilePath myInstances.mof # import and emit all CIM instances defined in this doc. 16 | # 17 | 18 | <# 19 | .Synopsis 20 | Reset the CIM class cache 21 | .DESCRIPTION 22 | Before a MOF file defining CIM instances can be read, the classes 23 | must be loaded into the class cache. Calling this cmdlet resets the 24 | class cache to the default classes 25 | .EXAMPLE 26 | Reset-CimClassCache 27 | .EXAMPLE 28 | Another example of how to use this cmdlet 29 | #> 30 | function Reset-CimClassCache 31 | { 32 | [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ClearCache() 33 | [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::LoadDefaultCimKeywords() 34 | } 35 | 36 | <# 37 | .Synopsis 38 | Adds the classes defined in a MOF file to the current set of classes 39 | .DESCRIPTION 40 | Before a MOF file defining CIM instances can be read, the classes 41 | must be loaded into the class cache. Calling this function will import classes 42 | from a MOF file and add them to the set of cached classes. It must be used 43 | to import the CIM class definition of any custom classes that might be 44 | contained in an instance document. 45 | .EXAMPLE 46 | Add-CachedCimClass ./mtFileDefininingCustomCimClasses.schema.mof 47 | #> 48 | function Add-CachedCimClass 49 | { 50 | [CmdletBinding(DefaultParameterSetName="FromMofFile")] 51 | param ( 52 | # The MOF file to import classes from 53 | [Parameter(ParameterSetName="FromMofFile", ValueFromPipelineByPropertyName, Position=0)] 54 | [alias('fullname', 'path')] 55 | [string] 56 | $MofClassFile, 57 | [Parameter(ParameterSetName="FromModule")] 58 | $Module 59 | ) 60 | 61 | process { 62 | $errors = New-Object System.Collections.ObjectModel.Collection[Exception] 63 | 64 | try 65 | { 66 | if ($PSCmdlet.ParameterSetName -eq "FromMofFile") 67 | { 68 | $resolvedMofPath = Resolve-Path -ErrorAction stop $MofClassFile 69 | [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportClasses($resolvedMofPath, $null, $errors) 70 | $errors | Write-Error 71 | } 72 | else 73 | { 74 | foreach ($mi in Get-Module -ListAvailable -Name $Module) 75 | { 76 | Write-Verbose -Verbose:$Verbose "Processing module $($module.Name)" 77 | $schemaFile = "" 78 | [void] [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportCimKeywordsFromModule($mi, $null, [ref] $schemaFile) 79 | if ($schemaFile) 80 | { 81 | Write-Verbose -Verbose:$verbose " Schema loaded from file: '$schemaFile'" 82 | } 83 | else 84 | { 85 | Write-Verbose -Verbose:$verbose " No schema file was found." 86 | } 87 | } 88 | } 89 | } 90 | catch 91 | { 92 | throw $_ 93 | } 94 | } 95 | } 96 | 97 | <# 98 | .Synopsis 99 | Returns Dump out all of the cached CIM classes 100 | .DESCRIPTION 101 | Before a MOF file defining CIM instances can be read, the classes 102 | must be loaded into the class cache or an error will occur, This function will 103 | .EXAMPLE 104 | Import-CimInstances ./myInstanceDoc.mof 105 | #> 106 | 107 | function Get-CachedCimClass 108 | { 109 | [CmdletBinding(DefaultParameterSetName="ByClassName")] 110 | param ( 111 | [Parameter(ParameterSetName="ByClassName", Position=0)] 112 | $ClassName = "*", 113 | [Parameter(ParameterSetName="ByFileName")] 114 | $FileName, 115 | [Parameter(ParameterSetName="ByModuleName")] 116 | $ModuleName, 117 | [Parameter(ParameterSetName="ListLoadedFiles")] 118 | [switch] 119 | $ListLoadedFiles 120 | ) 121 | 122 | switch ($PSCmdlet.ParameterSetName) 123 | { 124 | ByFileName { 125 | [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::GetCachedClassByFileName($FileName) 126 | break 127 | } 128 | ByModuleName { 129 | [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::GetCachedClassByModuleName($ModuleName) 130 | break 131 | } 132 | ListLoadedFiles { 133 | [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::GetLoadedFiles() 134 | break 135 | } 136 | default { 137 | [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::GetCachedClasses(). 138 | Where{$_.CimClassName -like $ClassName} 139 | break 140 | } 141 | } 142 | } 143 | 144 | <# 145 | .Synopsis 146 | Returns CIM instances defined in a MOF file. 147 | .DESCRIPTION 148 | This file will parse a MOF instance document and return the resulting instances. 149 | But before a MOF file defining CIM instances can be read, the classes 150 | must be loaded into the class cache or an error will occur 151 | .EXAMPLE 152 | Import-CimInstance ./myInstanceDoc.mof 153 | #> 154 | function Import-CimInstance 155 | { 156 | param ( 157 | [parameter(ValueFromPipelineByPropertyName)] 158 | [alias('fullname', 'path')] 159 | [string] 160 | $MofInstanceFilePath 161 | ) 162 | 163 | process { 164 | try 165 | { 166 | 167 | $resolvedMofPath = Resolve-Path -ErrorAction stop $MofInstanceFilePath 168 | [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportInstances($resolvedMofPath) 169 | } 170 | catch 171 | { 172 | Write-Error -Exception $_.Exception -Message "Error with $MofInstanceFilePath" 173 | } 174 | } 175 | } 176 | 177 | -------------------------------------------------------------------------------- /Tooling/DscConfiguration/ConvertFrom-EncryptedFile.ps1: -------------------------------------------------------------------------------- 1 | Function ConvertFrom-EncryptedFile 2 | { 3 | [cmdletbinding(DefaultParameterSetName='LocalCertStoreAndFilePath')] 4 | Param( 5 | [Parameter( 6 | Position=0, 7 | Mandatory = $true, 8 | ValueFromPipelineByPropertyName=$true, 9 | ParameterSetName = 'LocalCertStoreAndFilePath' 10 | )] 11 | [Parameter( 12 | Position=0, 13 | Mandatory = $true, 14 | ValueFromPipelineByPropertyName=$true, 15 | ParameterSetName = 'ArbitraryCertAndFilePath' 16 | )] 17 | [string] 18 | $Path, 19 | 20 | [Parameter( 21 | Position=0, 22 | Mandatory = $true, 23 | ValueFromPipeline=$true, 24 | ParameterSetName = 'LocalCertStoreAndInputObject' 25 | )] 26 | [Parameter( 27 | Position=0, 28 | Mandatory = $true, 29 | ValueFromPipeline=$true, 30 | ParameterSetName = 'ArbitraryCertAndInputObject' 31 | )] 32 | [System.IO.FileInfo] 33 | $InputObject, 34 | 35 | #Can be a path to the local cert store like Cert:\CurrentUser\My\9554F368FEA619A655A1D49408FC13C3E0D60E11 36 | [Parameter( 37 | mandatory=$true, 38 | position = 1, 39 | ParameterSetName = 'LocalCertStoreAndFilePath' 40 | )] 41 | [Parameter( 42 | mandatory=$true, 43 | position = 1, 44 | ParameterSetName = 'LocalCertStoreAndInputObject' 45 | )] 46 | [string] 47 | $CertificatePath, 48 | 49 | #Must be a System.Security.Cryptography.X509Certificates.X509Certificate2 object 50 | [Parameter( 51 | mandatory=$true, 52 | position = 1, 53 | ParameterSetName = 'ArbitraryCertAndInputObject' 54 | )] 55 | [Parameter( 56 | mandatory=$true, 57 | position = 1, 58 | ParameterSetName = 'ArbitraryCertAndFilePath' 59 | )] 60 | [System.Security.Cryptography.X509Certificates.X509Certificate2] 61 | $Certificate, 62 | [parameter()] 63 | [string] 64 | $FileExtension = 'encrypted' 65 | ) 66 | 67 | process 68 | { 69 | switch ($PSCmdlet.ParameterSetName) 70 | { 71 | 'LocalCertStoreAndFilePath' { Write-Verbose "Loading certificate from $CertificatePath"; $Certificate = Get-Item $CertificatePath } 72 | 'LocalCertStoreAndInputObject' { Write-Verbose "Loading certificate from $CertificatePath"; $Certificate = Get-Item $CertificatePath ; $Path = $InputObject.FullName } 73 | 'ArbitraryCertAndInputObject' { $Path = $InputObject.FullName } 74 | 'ArbitraryCertAndFilePath' { } 75 | } 76 | 77 | try 78 | { 79 | $Path = (Resolve-Path $Path -ErrorAction Stop).ProviderPath 80 | 81 | $AesProvider = New-Object System.Security.Cryptography.AesManaged 82 | $AesProvider.KeySize = 256 83 | $AesProvider.BlockSize = 128 84 | $AesProvider.Mode = [System.Security.Cryptography.CipherMode]::CBC 85 | [Byte[]]$LenKey = New-Object Byte[] 4 86 | [Byte[]]$LenIV = New-Object Byte[] 4 87 | 88 | If($Path.Split(".")[-1] -ne $FileExtension) 89 | { 90 | Write-Error "The file to decrypt must be named *.encrypted." 91 | Return 92 | } 93 | 94 | If($Certificate.HasPrivateKey -eq $False -or $Certificate.HasPrivateKey -eq $null) 95 | { 96 | Write-Error "The supplied certificate does not contain a private key, or it could not be accessed." 97 | Return 98 | } 99 | 100 | Try 101 | { 102 | $FileStreamReader = New-Object System.IO.FileStream("$Path", [System.IO.FileMode]::Open) 103 | } 104 | Catch 105 | { 106 | $message = "Unable to open input file $path for reading." 107 | throw $message 108 | } 109 | 110 | $FileStreamReader.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null 111 | $FileStreamReader.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null 112 | $FileStreamReader.Read($LenKey, 0, 3) | Out-Null 113 | $FileStreamReader.Seek(4, [System.IO.SeekOrigin]::Begin) | Out-Null 114 | $FileStreamReader.Read($LenIV, 0, 3) | Out-Null 115 | [Int]$LKey = [System.BitConverter]::ToInt32($LenKey, 0) 116 | [Int]$LIV = [System.BitConverter]::ToInt32($LenIV, 0) 117 | [Int]$StartC = $LKey + $LIV + 8 118 | [Int]$LenC = [Int]$FileStreamReader.Length - $StartC 119 | [Byte[]]$KeyEncrypted = New-Object Byte[] $LKey 120 | [Byte[]]$IV = New-Object Byte[] $LIV 121 | $FileStreamReader.Seek(8, [System.IO.SeekOrigin]::Begin) | Out-Null 122 | $FileStreamReader.Read($KeyEncrypted, 0, $LKey) | Out-Null 123 | $FileStreamReader.Seek(8 + $LKey, [System.IO.SeekOrigin]::Begin) | Out-Null 124 | $FileStreamReader.Read($IV, 0, $LIV) | Out-Null 125 | [Byte[]]$KeyDecrypted = $Certificate.PrivateKey.Decrypt($KeyEncrypted, $false) 126 | $Transform = $AesProvider.CreateDecryptor($KeyDecrypted, $IV) 127 | Try 128 | { 129 | $FileStreamWriter = New-Object System.IO.FileStream("$($path -replace '\.encrypted')", [System.IO.FileMode]::Create) 130 | } 131 | Catch 132 | { 133 | Write-Error "Unable to open output file for writing.`r`n$($_.Message)" 134 | $FileStreamReader.Close() 135 | Return 136 | } 137 | [Int]$Count = 0 138 | [Int]$Offset = 0 139 | [Int]$BlockSizeBytes = $AesProvider.BlockSize / 8 140 | [Byte[]]$Data = New-Object Byte[] $BlockSizeBytes 141 | $CryptoStream = New-Object System.Security.Cryptography.CryptoStream($FileStreamWriter, $Transform, [System.Security.Cryptography.CryptoStreamMode]::Write) 142 | Do 143 | { 144 | $Count = $FileStreamReader.Read($Data, 0, $BlockSizeBytes) 145 | $Offset += $Count 146 | $CryptoStream.Write($Data, 0, $Count) 147 | } 148 | While ($Count -gt 0) 149 | $CryptoStream.FlushFinalBlock() 150 | } 151 | catch 152 | { 153 | throw $_ 154 | } 155 | finally 156 | { 157 | $CryptoStream.Close() 158 | $FileStreamWriter.Close() 159 | $FileStreamReader.Close() 160 | } 161 | Get-Item "$($path -replace '\.encrypted')" 162 | } 163 | } 164 | 165 | 166 | -------------------------------------------------------------------------------- /Tooling/DscDevelopment/New-MofFile.ps1: -------------------------------------------------------------------------------- 1 | function New-MofFile 2 | { 3 | <# 4 | .Synopsis 5 | Generates a MOF Class declaration for a DSC Resource 6 | .DESCRIPTION 7 | Uses the parameters of Set-TargetResource in a DSC Resource Module to generate a MOF schema file for use in DSC. 8 | .EXAMPLE 9 | New-MofFile -Name d:\source\dsc-prod\resources\baseresources\dscresources\Pagefile 10 | #> 11 | param ( 12 | [parameter()] 13 | [string] 14 | $Path, 15 | $Version = '1.0' , 16 | 17 | [Parameter()] 18 | [ValidateNotNullOrEmpty()] 19 | [string] 20 | $FriendlyName , 21 | 22 | [Switch] 23 | $LoadTypes 24 | ) 25 | 26 | $ResourceName = Split-Path $Path -Leaf 27 | 28 | if (!$FriendlyName) { 29 | $FriendlyName = $ResourceName 30 | } 31 | 32 | $ResourcePath = Join-Path $Path "$ResourceName.psm1" 33 | 34 | Write-Verbose "Attempting to parse $ResourcePath." 35 | try 36 | { 37 | $CommandAst = [System.Management.Automation.Language.Parser]::ParseFile($ResourcePath, [ref]$null, [ref]$null) 38 | $SetTargetResourceAst = $CommandAst.FindAll( 39 | {$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]}, 40 | $false 41 | ) | 42 | Where-Object {$_.name -like 'Set-TargetResource'} 43 | 44 | $ParametersAst = $SetTargetResourceAst.Body.ParamBlock.Parameters 45 | 46 | # Look for type definitions and execute them in this context 47 | # This could be very buggy. 48 | if ($LoadTypes) { 49 | Write-Warning "Types added to session with -LoadType will remain in the session until it ends." 50 | $CommandAst.FindAll( 51 | {$args[0] -is [System.Management.Automation.Language.CommandAst]}, 52 | $false 53 | ) | 54 | Where-Object { $_.CommandElements[0].Value -ieq 'Add-Type' } | 55 | ForEach-Object { 56 | Write-Verbose "Adding type:`n`n$($_.ToString())" 57 | Invoke-Expression $_.ToString() -ErrorAction Continue 58 | } 59 | } 60 | 61 | $Template = @" 62 | [ClassVersion("$Version"), FriendlyName("$FriendlyName")] 63 | class $ResourceName : OMI_BaseResource 64 | { 65 | 66 | "@ 67 | foreach ($ParameterAst in $ParametersAst) 68 | { 69 | $PropertyString = '[write' 70 | $IsKey = $false 71 | 72 | $ParameterName = $ParameterAst.Name -replace '\$' 73 | Write-Verbose "Processing $ParameterName." 74 | 75 | $ParameterAttributesAst = $ParameterAst.Attributes | 76 | Where-Object {$_ -is [System.Management.Automation.Language.AttributeAst]} 77 | $ParameterTypeAttributeAst = $ParameterAst.Attributes | 78 | Where-Object {$_ -is [System.Management.Automation.Language.TypeConstraintAst]} 79 | 80 | switch ($ParameterAttributesAst) 81 | { 82 | {($_.typename -like 'parameter') -and (($_.NamedArguments.ArgumentName) -contains 'Mandatory')} { 83 | Write-Verbose "Parameter - $ParameterName is Mandatory." 84 | $PropertyString = '[Key' 85 | $IsKey = $true 86 | } 87 | } 88 | 89 | switch ($ParameterAttributesAst) 90 | { 91 | {$_.typename -like 'ValidateSet'} { 92 | Write-Verbose "Parameter - $ParameterName has a validate set." 93 | $oldOFS = $OFS 94 | $OFS = '", "' 95 | $SingleQuote = "'" 96 | $ValidValues = "$($_.PositionalArguments.Value -replace $SingleQuote)" 97 | $PropertyString += @" 98 | ,ValueMap{"$ValidValues"},Values{"$ValidValues"} 99 | "@ 100 | $OFS = $oldOFS 101 | } 102 | } 103 | 104 | Write-Verbose "Parameter - $ParameterName is typed with $($ParameterTypeAttributeAst.TypeName)." 105 | 106 | $type = $ParameterTypeAttributeAst.TypeName.FullName -as [Type] 107 | 108 | $table = @{ 109 | [string] = 'string' 110 | [string[]] = 'string' 111 | [switch] = 'boolean' 112 | [bool] = 'boolean' 113 | [boolean[]] = 'boolean' 114 | [long] = 'sint64' 115 | [long[]] = 'sint64' 116 | [int] = 'sint32' 117 | [int[]] = 'sint32' 118 | [byte] = 'uint8' 119 | [byte[]] = 'uint8' 120 | [uint32] = 'uint32' 121 | [uint32[]] = 'uint32' 122 | [uint64] = 'uint64' 123 | [uint64[]] = 'uint64' 124 | } 125 | 126 | if ($table.ContainsKey($type)) 127 | { 128 | $PropertyString += "] $($table[$type]) " 129 | } 130 | elseif ($type -eq [pscredential]) 131 | { 132 | $PropertyString += ',EmbeddedInstance("MSFT_Credential")] string ' 133 | } 134 | else 135 | { 136 | $goodType = $false 137 | 138 | if ($null -ne $type -and $type.IsEnum) 139 | { 140 | Write-Verbose "'$type' is an Enum type. Let's convert it into a ValueMap." 141 | 142 | $eNames = ($type.GetEnumNames() | ForEach-Object { "`"$_`"" }) -join ',' 143 | $eValues = ($type.GetEnumValues().value__ | ForEach-Object { "`"$_`"" }) -join ',' 144 | $eType = $type.GetEnumUnderlyingType() 145 | 146 | if ($table.ContainsKey($eType)) 147 | { 148 | $goodType = $true 149 | $PropertyString += ",ValueMap{$eValues},Values{$eNames}] $($table[$eType]) " 150 | } 151 | } 152 | 153 | if (-not $goodType) 154 | { 155 | Write-Warning "Don't know what to do with $($ParameterTypeAttributeAst.TypeName.FullName)" 156 | } 157 | } 158 | 159 | $arrayString = if ($type.IsArray) { '[]' } else { '' } 160 | 161 | $Template += $PropertyString + "$ParameterName$arrayString;`r`n" 162 | } 163 | 164 | $Template += @' 165 | }; 166 | '@ 167 | 168 | $TargetPath = join-path $Path "$ResourceName.schema.mof" 169 | 170 | if (Test-Path $TargetPath) 171 | { 172 | Write-Verbose "Removing previous file from $TargetPath." 173 | Remove-Item -Path $TargetPath -Force 174 | } 175 | 176 | Write-Verbose "Writing $ResourceName.schema.mof to $Path" 177 | 178 | $Template | 179 | Out-File -Encoding ascii -FilePath $TargetPath 180 | } 181 | catch 182 | { 183 | throw $_ 184 | } 185 | } 186 | 187 | 188 | -------------------------------------------------------------------------------- /README.old.md: -------------------------------------------------------------------------------- 1 | PowerShell Community DSC Modules 2 | =========== 3 | 4 | Desired State Configuration Modules to augment the initial offering in PowerShell V4 5 | 6 | Check out my blog series on DSC at PowerShell.org: 7 | - Overview () 8 | - Configuring the Pull Server (REST version) () 9 | - Creating Configurations (, ) 10 | - Configuring Clients () 11 | - Building Custom Resources () 12 | - Packaging Custom Resources 13 | - Advanced Client Targeting 14 | 15 | 16 | ToDo 17 | ===== 18 | - [x] Initial upload 19 | - [x] Make New-MofFile handle more data types and complex data types 20 | - [x] Improve the DSC Module creation documentation for General Availability 21 | - [ ] Add samples of complete configurations 22 | - [ ] Add samples of composite configurations 23 | - [ ] MORE Modules! 24 | 25 | ###Required Modules 26 | In order to run some of the code in this pack some outside resources are needed. Make sure the following are installed on the dev system. 27 | 28 | * Pester () 29 | 30 | 31 | Getting Started With DSC Modules 32 | -------------------------------- 33 | 34 | 35 | NOTE: 36 | 37 | 38 | There are two sets of MOF (Managed Object Framework) files generated in this process. Each provider module has a MOF file defining a class representing the parameters to the functions in provider module. 39 | 40 | The second type of MOF file is created by running the configuration, with one generated per target node. This MOF file is the serialization of the configuration defined in MOF format. It references the MOF classes defined in the provider modules schema files and contains the values supplied in the configuration associated to the appropriate parameters. 41 | 42 | The general flow of Resource Processing in DSC (from Configuration MOF to Desired State) 43 | 44 | When you run the configuration function, a MOF file for each node is generated. This describes the state the machine should be in after processing the MOF file. The generated configuration MOF will note the resources used and their host module (see below). 45 | 46 | For each resource defined, the DSC engine uses the classes defined in the MOF to marshal parameters to call the PowerShell DSC resource (which is basically a PowerShell module). 47 | 48 | The DSC engine calls Test-TargetResource with the parameters defined in the MOF file (as mapped in the schema MOF). If Test-TargetResource returns $false, then Set-TargetResource is called with the same parameter set. 49 | 50 | ###DSC Resources 51 | 52 | DSC Resources are nested in a module (which I'll call the host module). Resources are located in a subfolder in a host module called DscResources. One host module can contain zero or more DSC Resources. 53 | 54 | DSC Resources are modules as well, but by not being located directly on the PSModulePath, they won't clutter up your environment. 55 | 56 | ####Versioning 57 | 58 | DSC Resources are versioned by the module version of their host module. 59 | 60 | 61 | ####Naming 62 | 63 | The module name will be the resource name when configurations are defined, unless you specify an alias name for the module in the MOF schema (detailed below). 64 | 65 | 66 | ####Functions 67 | 68 | - Set-TargetResource 69 | - Set-TargetResource is called if Test-TargetResource (described in a bit) returns false. Test-TargetResource is called with the same parameters as Set-TargetResource. 70 | - This function implements the change requested. You'll need to support both the case of Ensure = 'Present' and Ensure = 'Absent'. If this resource represents a multi-step process and that process needs to support suspend/resume or requires reboots, it may be an indication that you want to break it into several resources, otherwise you'll have to implement the stage checking in this function. 71 | - Each parameter for this function will need to be modeled in the CIM schema in a CIM class named for the resource type, so if you expect structured objects, you'll need to define comprehensive schema documents. 72 | - Logging for this function occurs in the form of verbose output (which is written to the DSC event log). 73 | - While the DSC engine should only call this function if Test-TargetResource returns false, it would be prudent to write the system state changes in as idempotent a manner as possible. 74 | 75 | - Test-TargetResource 76 | - Test-TargetResource validates whether a resource configuration is in the desired state. 77 | - Test-TargetResource offers the same parameters as Set-TargetResource. 78 | - Test-TargetResource evaluate the final state of the resource (not intermediate steps) and returns a $true if the configuration matches or $false if the configuration does not. 79 | - This function needs to support both Ensure = 'Present' and Ensure = 'Absent' declarations. 80 | 81 | - Get-TargetResource 82 | - This function inventories the resource based on the key values (mandatory parameters) for the CIM schema. 83 | - Get-TargetResource returns a hashtable containing the values that match the current state of the resource configuration. 84 | - Get-TargetResource only needs to support parameters that are noted as key values in the schema.mof file (mandatory parameters in the Set-TargetResource and Get-TargetResource functions). 85 | 86 | 87 | 88 | ###The Schema 89 | 90 | The final bit of creating a provider module is the MOF schema file. The MOF schema file defines a CIM class used for serializing the parameter values from the configuration file and deserializing to apply as parameters to the call the above functions. 91 | 92 | Detailed documentation about MOF datatypes can be found here - [http://msdn.microsoft.com/en-us/library/cc250850.aspx](http://msdn.microsoft.com/en-us/library/cc250850.aspx) 93 | 94 | 95 | 96 | ####In creating the MOF schema file, there are a couple of rules. 97 | 98 | - All resource classes (those that represent the parameters for the Set-TargetResource) must inherit from OMI_BaseResource. 99 | - The file is named {resource}.schema.mof 100 | - Classes are attributed with a version number which is currently meaningless 101 | 102 | - Classes can be attributed with a FriendlyName, which would be the name that the resource would use in the configuration declaration. The full class name is used in the generated configuration MOF. 103 | - Mandatory parameters are annotated as [Key] values. 104 | - Other parameters are annotated as [Write] values. 105 | - If there is a ValidateSet or Enumeration, they are represented in a ValueMap and Values combination as part of the Write annotation. 106 | 107 | - The file encoding has to be Unicode or ASCII. UTF8 will fail validation. 108 | 109 | 110 | Validation of the MOF file should be done by running: 111 | 112 | mofcomp.exe -check {path to your mof file}. 113 | 114 | 115 | 116 | ####Example of a very basic schema.mof file: 117 | 118 | ```` 119 | [version("1.0.0"), FriendlyName("PowerPlan")] 120 | 121 | class PowerPlan : OMI_BaseResource 122 | 123 | { 124 | 125 | [Key] string Name; 126 | 127 | [write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] string Ensure; 128 | 129 | }; 130 | ```` 131 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Resolve-ConfigurationProperty.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.ps1", ".psm1") 3 | if (-not (Test-Path $sut)) 4 | { 5 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.ps1", ".ps1") 6 | } 7 | $pathtosut = join-path $here $sut 8 | if (-not (Test-Path $pathtosut)) 9 | { 10 | Write-Error "Failed to find script to test at $pathtosut" 11 | } 12 | 13 | 14 | iex ( gc $pathtosut -Raw ) 15 | 16 | describe 'how Resolve-DscConfigurationProperty responds' { 17 | 18 | $ConfigurationData = @{ 19 | AllNodes = @(); 20 | SiteData = @{}; 21 | Services = @{}; 22 | Applications = @{}; 23 | } 24 | $ConfigurationData.SiteData = @{ NY = @{ PullServerPath = 'ConfiguredBySite' } } 25 | context 'when a node has an override for a site property' { 26 | $Node = @{ 27 | Name = 'TestBox' 28 | Location = 'NY' 29 | PullServerPath = 'ConfiguredByNode' 30 | } 31 | 32 | $result = Resolve-DscConfigurationProperty -Node $Node -PropertyName 'PullServerPath' 33 | 34 | it "should return the node's override" { 35 | $result | should be 'ConfiguredByNode' 36 | } 37 | } 38 | 39 | context 'when a node does not override the site property' { 40 | $Node = @{ 41 | Name = 'TestBox' 42 | Location = 'NY' 43 | } 44 | 45 | $result = Resolve-DscConfigurationProperty -Node $Node -PropertyName 'PullServerPath' 46 | it "should return the site's default value" { 47 | $result | should be 'ConfiguredBySite' 48 | } 49 | } 50 | 51 | context 'when a specific site does not have the property but the base configuration data does' { 52 | $ConfigurationData.SiteData = @{ 53 | All = @{ PullServerPath = 'ConfiguredByDefault' } 54 | NY = @{ PullServerPath = 'ConfiguredBySite' } 55 | } 56 | 57 | $Node = @{ 58 | Name = 'TestBox' 59 | Location = 'OR' 60 | } 61 | 62 | $result = Resolve-DscConfigurationProperty -Node $Node -PropertyName 'PullServerPath' 63 | it "should return the site's default value" { 64 | $result | should be 'ConfiguredByDefault' 65 | } 66 | } 67 | } 68 | 69 | describe 'how Resolve-DscConfigurationProperty (services) responds' { 70 | $ConfigurationData = @{AllNodes = @(); SiteData = @{} ; Services = @{}; Applications = @{}} 71 | 72 | $ConfigurationData.Services = @{ 73 | MyTestService = @{ 74 | DataSource = 'MyDefaultValue' 75 | } 76 | } 77 | 78 | context 'when a default value is supplied for a service and node has a property override' { 79 | 80 | $Node = @{ 81 | Name = 'TestBox' 82 | Location = 'NY' 83 | Services = @{ 84 | MyTestService = @{ 85 | DataSource = 'MyCustomValue' 86 | } 87 | } 88 | } 89 | 90 | $result = Resolve-DscConfigurationProperty -Node $Node -ServiceName MyTestService -PropertyName DataSource 91 | 92 | it 'should return the override from the node' { 93 | $result | should be 'MyCustomValue' 94 | 95 | } 96 | } 97 | 98 | context 'when a site level override is present' { 99 | $ConfigurationData.SiteData = @{ 100 | NY = @{ 101 | Services = @{ 102 | MyTestService = @{ 103 | DataSource = 'MySiteValue' 104 | } 105 | } 106 | } 107 | } 108 | $Node = @{ 109 | Name = 'TestBox' 110 | Location = 'NY' 111 | } 112 | 113 | $result = Resolve-DscConfigurationProperty -Node $Node -ServiceName MyTestService -PropertyName DataSource 114 | 115 | it 'should return the override from the site' { 116 | $result | should be 'MySiteValue' 117 | } 118 | } 119 | 120 | context 'when a global site level override is present' { 121 | $ConfigurationData.SiteData = @{ 122 | All = @{ 123 | Services = @{ 124 | MyTestService = @{ 125 | DataSource = 'FromAllSite' 126 | } 127 | } 128 | } 129 | NY = @{ 130 | Services = @{ 131 | MyTestService = @{} 132 | } 133 | } 134 | } 135 | $ConfigurationData.Services = @{ 136 | MyTestService = @{} 137 | } 138 | $Node = @{ 139 | Name = 'TestBox' 140 | Location = 'NY' 141 | } 142 | 143 | $result = Resolve-DscConfigurationProperty -Node $Node -ServiceName MyTestService -PropertyName DataSource 144 | 145 | it 'should return the override from the site' { 146 | $result | should be 'FromAllSite' 147 | 148 | } 149 | } 150 | 151 | context 'when no node or site level override is present' { 152 | $ConfigurationData.Services = @{ 153 | MyTestService = @{ 154 | DataSource = 'MyDefaultValue' 155 | } 156 | } 157 | $ConfigurationData.SiteData = @{ 158 | All = @{ DataSource = 'NotMyDefaultValue'} 159 | NY = @{ 160 | Services = @{ 161 | MyTestService = @{} 162 | } 163 | } 164 | } 165 | $Node = @{ 166 | Name = 'TestBox' 167 | Location = 'NY' 168 | } 169 | 170 | $result = Resolve-DscConfigurationProperty -Node $Node -ServiceName MyTestService -PropertyName DataSource 171 | 172 | it 'should return the default value from the service' { 173 | $result | should be 'MyDefaultValue' 174 | 175 | } 176 | } 177 | 178 | context 'when no service default is specified' { 179 | 180 | $Node = @{ 181 | Name = 'TestBox' 182 | Location = 'NY' 183 | MissingFromFirstServiceConfig = 'FromNodeWithoutService' 184 | } 185 | 186 | $result = Resolve-DscConfigurationProperty -Node $Node -ServiceName MyTestService -PropertyName MissingFromFirstServiceConfig 187 | it 'should fall back to checking for the parameter without the service name' { 188 | $result | should be 'FromNodeWithoutService' 189 | } 190 | } 191 | 192 | context 'when two services are specified default is specified' { 193 | $ConfigurationData.Services = @{ 194 | MyTestService = @{} 195 | MySecondTestService = @{ 196 | MissingFromFirstServiceConfig = 'FromSecondServiceConfig' 197 | } 198 | } 199 | $Node = @{ 200 | Name = 'TestBox' 201 | Location = 'NY' 202 | } 203 | 204 | $result = Resolve-DscConfigurationProperty -Node $Node -ServiceName MyTestService, MySecondTestService -PropertyName MissingFromFirstServiceConfig 205 | 206 | it 'should retrieve the parameter from the second service before falling back to the node' { 207 | $result | should be 'FromSecondServiceConfig' 208 | } 209 | } 210 | } 211 | 212 | describe 'how Resolve-DscConfigurationProperty (applications) responds' { 213 | $ConfigurationData = @{AllNodes = @(); SiteData = @{} ; Services = @{}; Applications = @{}} 214 | $ConfigurationData.Applications = @{ 215 | Git = @{ 216 | LocalPath = 'c:\installs\Git\' 217 | InstallerName = 'setup.exe' 218 | SourcePath = 'c:\global\git\setup.exe' 219 | } 220 | Mercurial = @{ 221 | LocalPath = 'c:\installs\Mercurial\' 222 | SourcePath = 'c:\global\Mercurial\setup.exe' 223 | InstallerName = 'Setup.exe' 224 | } 225 | WinMerge = @{ 226 | LocalPath = 'c:\installs\winmerge\' 227 | InstallerName = 'setup.exe' 228 | SourcePath = 'c:\global\winmerge\setup.exe' 229 | } 230 | } 231 | $ConfigurationData.SiteData.NY = @{ 232 | Applications = @{ 233 | Mercurial = @{ 234 | LocalPath = 'c:\installs\Mercurial\' 235 | SourcePath = 'c:\site\Mercurial\setup.exe' 236 | InstallerName = 'Setup.exe' 237 | } 238 | WinMerge = @{ 239 | LocalPath = 'c:\installs\winmerge\' 240 | InstallerName = 'setup.exe' 241 | SourcePath = 'c:\site\winmerge\setup.exe' 242 | } 243 | } 244 | } 245 | $Node = @{ 246 | Name = 'TestBox' 247 | Location = 'NY' 248 | Applications = @{ 249 | WinMerge = @{ 250 | LocalPath = 'c:\installs\winmerge\' 251 | InstallerName = 'setup.exe' 252 | SourcePath = 'c:\node\winmerge\setup.exe' 253 | } 254 | } 255 | } 256 | context 'When there is a base setting for an application' { 257 | 258 | $result = Resolve-DscConfigurationProperty -Node $Node -Application 'Git' 259 | 260 | it 'should return the application level configuration' { 261 | $result.SourcePath | should be 'c:\global\git\setup.exe' 262 | } 263 | } 264 | 265 | context 'When there is a site level override for the base setting for an application' { 266 | 267 | $result = Resolve-DscConfigurationProperty -Node $Node -Application 'Mercurial' 268 | 269 | it 'should return the site application level configuration' { 270 | $result.SourcePath | should be 'c:\site\Mercurial\setup.exe' 271 | } 272 | } 273 | 274 | context 'When there is a node level override for the base setting for an application' { 275 | 276 | $result = Resolve-DscConfigurationProperty -Node $Node -Application 'WinMerge' 277 | 278 | it 'should return the node application level configuration' { 279 | $result.SourcePath | should be 'c:\node\winmerge\setup.exe' 280 | } 281 | } 282 | } 283 | #<# 284 | describe 'how Resolve-DscConfigurationProperty (applications/services) responds' { 285 | $ConfigurationData = @{AllNodes = @(); SiteData = @{} ; Services = @{}; Applications = @{}} 286 | $ConfigurationData.Applications.Sublime = @{ 287 | LocalPath = 'c:\installs\Sublime\' 288 | InstallerName = 'setup.exe' 289 | SourcePath = 'c:\default\Sublime\setup.exe' 290 | } 291 | $ConfigurationData.Services = @{ 292 | BuildAgent = @{ 293 | Applications = @{ 294 | Git = @{ 295 | LocalPath = 'c:\installs\Git\' 296 | InstallerName = 'setup.exe' 297 | SourcePath = 'c:\global\git\setup.exe' 298 | } 299 | Mercurial = @{ 300 | LocalPath = 'c:\installs\Mercurial\' 301 | SourcePath = 'c:\global\Mercurial\setup.exe' 302 | InstallerName = 'Setup.exe' 303 | } 304 | WinMerge = @{ 305 | LocalPath = 'c:\installs\winmerge\' 306 | InstallerName = 'setup.exe' 307 | SourcePath = 'c:\global\winmerge\setup.exe' 308 | } 309 | } 310 | } 311 | } 312 | $ConfigurationData.SiteData.NY = @{ 313 | Services = @{ 314 | BuildAgent = @{ 315 | Applications = @{ 316 | Mercurial = @{ 317 | LocalPath = 'c:\installs\Mercurial\' 318 | SourcePath = 'c:\site\Mercurial\setup.exe' 319 | InstallerName = 'Setup.exe' 320 | } 321 | WinMerge = @{ 322 | LocalPath = 'c:\installs\winmerge\' 323 | InstallerName = 'setup.exe' 324 | SourcePath = 'c:\site\winmerge\setup.exe' 325 | } 326 | } 327 | } 328 | } 329 | } 330 | $Node = @{ 331 | Name = 'TestBox' 332 | Location = 'NY' 333 | Services = @{ 334 | BuildAgent = @{ 335 | Applications = @{ 336 | WinMerge = @{ 337 | LocalPath = 'c:\installs\winmerge\' 338 | InstallerName = 'setup.exe' 339 | SourcePath = 'c:\node\winmerge\setup.exe' 340 | } 341 | } 342 | } 343 | } 344 | } 345 | context 'When there is a base setting for an application' { 346 | 347 | $result = Resolve-DscConfigurationProperty -Node $Node -Application 'Git' -ServiceName 'BuildAgent' 348 | 349 | it 'should return the application level configuration' { 350 | $result.SourcePath | should be 'c:\global\git\setup.exe' 351 | } 352 | } 353 | 354 | context 'When there is a site level override for the base setting for an application' { 355 | 356 | $result = Resolve-DscConfigurationProperty -Node $Node -Application 'Mercurial' -ServiceName 'BuildAgent' 357 | 358 | it 'should return the site application level configuration' { 359 | $result.SourcePath | should be 'c:\site\Mercurial\setup.exe' 360 | } 361 | } 362 | 363 | context 'When there is a node level override for the base setting for an application' { 364 | 365 | $result = Resolve-DscConfigurationProperty -Node $Node -Application 'WinMerge' -ServiceName 'BuildAgent' 366 | 367 | it 'should return the node application level configuration' { 368 | $result.SourcePath | should be 'c:\node\winmerge\setup.exe' 369 | } 370 | } 371 | 372 | context 'When there is no service level setting for an application, but there is a default config' { 373 | 374 | $result = Resolve-DscConfigurationProperty -Node $Node -Application 'Sublime' -ServiceName 'BuildAgent' 375 | 376 | it 'should return the node application level configuration' { 377 | $result.SourcePath | should be 'c:\default\Sublime\setup.exe' 378 | } 379 | } 380 | } 381 | #> 382 | -------------------------------------------------------------------------------- /Tooling/dscbuild/Resolve-ConfigurationProperty.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-DscConfigurationProperty { 2 | <# 3 | .Synopsis 4 | Searches DSC metadata 5 | .Description 6 | Longer description of the command 7 | .Example 8 | 9 | #> 10 | [cmdletbinding()] 11 | param ( 12 | #The current node being evaluated for the specified property or application. 13 | [parameter()] 14 | [System.Collections.Hashtable] 15 | $Node, 16 | 17 | #The service(s) that will be checked for the specified property or application. 18 | [parameter()] 19 | [ValidateNotNullOrEmpty()] 20 | [string[]] 21 | $ServiceName, 22 | 23 | #The application metadata that will be checked for. 24 | [parameter()] 25 | [ValidateNotNullOrEmpty()] 26 | [string] 27 | $Application, 28 | 29 | #The property that will be checked for. 30 | [parameter()] 31 | [ValidateNotNullOrEmpty()] 32 | [string] 33 | $PropertyName, 34 | 35 | #By default, all results must return just one entry. If multiple results are allowed, this flag must be enabled. 36 | [parameter()] 37 | [switch] 38 | $AllowMultipleResults, 39 | 40 | #If you want to override the default behavior of checking up-scope for configuration data, it can be supplied here. 41 | [parameter()] 42 | [System.Collections.Hashtable] 43 | $ConfigurationData 44 | ) 45 | 46 | Write-Verbose "" 47 | if (-not $PSBoundParameters.ContainsKey('ConfigurationData')) { 48 | Write-Verbose "" 49 | Write-Verbose "Resolving ConfigurationData" 50 | $ScopeToCheck = 1 51 | do { 52 | try { 53 | $ConfigurationData = Get-Variable -scope $ScopeToCheck -Name 'ConfigurationData' -ValueOnly -ErrorAction Stop 54 | } 55 | catch { 56 | Write-Verbose "`t`tNothing in scope $ScopeToCheck for ConfigurationData" 57 | } 58 | $ScopeToCheck++ 59 | } until ($ScopeToCheck -gt 5 -or ($ConfigurationData -is [hashtable] -and $ConfigurationData.Keys.Count -gt 0)) 60 | 61 | if ($ConfigurationData -isnot [hashtable] -or $ConfigurationData.Keys.Count -eq 0) { 62 | throw 'Failed to resolve ConfigurationData. Please confirm that $ConfigurationData is property set in a scope above this Resolve-DscConfigurationProperty or passed to Resolve-DscConfigurationProperty via the ConfigurationData parameter.' 63 | } 64 | else { 65 | $PSBoundParameters.Add('ConfigurationData', $ConfigurationData) 66 | } 67 | } 68 | Write-Verbose "Starting to evaluate $($Node.Name) for PropertyName: $PropertyName Application: $Application From Services: $ServiceName" 69 | 70 | $Value = @() 71 | if (($Node -ne $null)) { 72 | $Value = Assert-NodeOverride @PSBoundParameters 73 | Write-Verbose "Value after checking the node is $Value" 74 | } 75 | if (-not $PSBoundParameters.ContainsKey('Application')) { 76 | $Value = ($Value | where-object {-not [string]::IsNullOrEmpty($_)}) 77 | } 78 | 79 | if ($Value.count -eq 0) { 80 | $Value += Assert-SiteOverride @PSBoundParameters 81 | Write-Verbose "Value after checking the site is $Value" 82 | } 83 | if (-not $PSBoundParameters.ContainsKey('Application')) { 84 | $Value = ($Value | where-object {-not [string]::IsNullOrEmpty($_)}) 85 | } 86 | 87 | if ($Value.count -eq 0) { 88 | $Value += Assert-GlobalSetting @PSBoundParameters 89 | Write-Verbose "Value after checking the global is $Value" 90 | } 91 | if (-not $PSBoundParameters.ContainsKey('Application')) { 92 | $Value = ($Value | where-object {-not [string]::IsNullOrEmpty($_)}) 93 | } 94 | 95 | if (-not $PSBoundParameters.ContainsKey('Application')) { 96 | if (($Value.count -eq 0) -and ($ServiceName.Count -gt 0)) 97 | { 98 | $PSBoundParameters.Remove('ServiceName') | out-null 99 | $Value = Resolve-DscConfigurationProperty @PSBoundParameters 100 | } 101 | 102 | if ($Value.count -eq 0) 103 | { 104 | throw "Failed to resolve $PropertyName for $($Node.Name). Please update your node, service, site, or all sites with a default value." 105 | } 106 | 107 | if ($AllowMultipleResults) { 108 | return $Value 109 | } 110 | elseif ((-not $AllowMultipleResults) -and ($Value.count -gt 1)) { 111 | throw "More than one result was returned for $PropertyName for $($Node.Name). Verify that your property configurations are correct. If multiples are to be allowed, use -AllowMultipleResults." 112 | } 113 | else { 114 | return $Value 115 | } 116 | } 117 | else { 118 | if ($value -eq $null) { 119 | $PSBoundParameters.Remove('ServiceName') | out-null 120 | $Value = Resolve-DscConfigurationProperty @PSBoundParameters 121 | } 122 | if ($value -is [System.Collections.Hashtable]) { 123 | return $value 124 | } 125 | else { 126 | throw "Failed to resolve $Application for $($Node.Name). Please update your node, service, site, or all sites with a default value." 127 | } 128 | } 129 | } 130 | 131 | Set-Alias -Name 'Resolve-ConfigurationProperty' -Value 'Resolve-DscConfigurationProperty' 132 | 133 | function Test-HashtableKey { 134 | [cmdletbinding()] 135 | param ( 136 | [parameter(position=0)] 137 | [System.Collections.Hashtable] 138 | $Hashtable, 139 | [parameter(position=1)] 140 | [string] 141 | $key, 142 | $NumberOfTabs 143 | ) 144 | if ($Hashtable -ne $null) { 145 | #Write-Verbose (("`t" * $NumberOfTabs) + "$((Get-PSCallStack)[1].Command)") 146 | #Write-Verbose (("`t" * $NumberOfTabs) + "$((Get-PSCallStack)[1].ScriptLineNumber)") 147 | $ofs = ', ' 148 | Write-Verbose (("`t" * $NumberOfTabs) + "Testing for $key from ( $($Hashtable.keys) )") 149 | $Found = $Hashtable.ContainsKey($key) 150 | Write-Verbose (("`t" * $NumberOfTabs) + "$key was found: $Found") 151 | return $found 152 | } 153 | return $false 154 | } 155 | 156 | function Test-ApplicationKey { 157 | param ( 158 | [System.Collections.Hashtable] 159 | $Hashtable, 160 | [string] 161 | $Application 162 | ) 163 | $NumberOfTabs = 3 164 | if ($Hashtable -ne $null) { 165 | if (-not [string]::IsNullOrEmpty($Application)) { 166 | if (Test-HashtableKey $Hashtable 'Applications' -NumberOfTabs $NumberOfTabs) { 167 | $NumberOfTabs++ 168 | if (Test-HashtableKey $Hashtable['Applications'] $Application -NumberOfTabs $NumberOfTabs) { 169 | Write-Verbose ("`t" * $NumberOfTabs + "Found $Application") 170 | return $true 171 | } 172 | } 173 | } 174 | } 175 | return $false 176 | } 177 | 178 | function Test-ServiceKey { 179 | param ( 180 | [System.Collections.Hashtable] 181 | $Hashtable, 182 | [string] 183 | $Service, 184 | [string] 185 | $PropertyName 186 | ) 187 | $NumberOfTabs = 3 188 | if ($Hashtable -ne $null) { 189 | if (-not [string]::IsNullOrEmpty($Service)) { 190 | if (Test-HashtableKey $Hashtable 'Services' -NumberOfTabs $NumberOfTabs) { 191 | $NumberOfTabs++ 192 | if (Test-HashtableKey $Hashtable['Services'] $Service -NumberOfTabs $NumberOfTabs) { 193 | $NumberOfTabs++ 194 | if (Test-HashtableKey $Hashtable['Services'][$Service] $PropertyName -NumberOfTabs $NumberOfTabs) { 195 | return $true 196 | } 197 | } 198 | } 199 | } 200 | } 201 | return $false 202 | } 203 | 204 | 205 | function Resolve-HashtableProperty { 206 | param ( 207 | [System.Collections.Hashtable] 208 | $Hashtable, 209 | [string] 210 | $PropertyName 211 | ) 212 | 213 | if ($Hashtable -ne $null) { 214 | $PropertyValue = $Hashtable[$PropertyName] 215 | if ($PropertyValue -ne $null) { 216 | Write-Verbose "`t`t`t$Found PropertyName $PropertyName with value $PropertyValue" 217 | return $PropertyValue 218 | } 219 | } 220 | } 221 | 222 | function Assert-NodeOverride { 223 | [cmdletbinding()] 224 | param ( 225 | [System.Collections.Hashtable] 226 | $Node, 227 | [string[]] 228 | $ServiceName, 229 | [string] 230 | $Application, 231 | [string] 232 | $PropertyName, 233 | [switch] 234 | $AllowMultipleResults, 235 | [System.Collections.Hashtable] 236 | $ConfigurationData 237 | ) 238 | $Value = @() 239 | Write-Verbose "`tChecking Node: $($Node.Name)" 240 | if (( $ServiceName.count -eq 0 ) -and 241 | ( -not [string]::IsNullOrEmpty($Application) ) -and 242 | ( Test-ApplicationKey -Hashtable $Node -Application $Application )) { 243 | 244 | $Value += Resolve-HashtableProperty $Node['Applications'] $Application 245 | 246 | } 247 | elseif (($ServiceName.count -eq 0) -and 248 | ( Test-HashtableKey $Node $PropertyName -NumberOfTabs 2)) { 249 | $Value += Resolve-HashtableProperty $Node $PropertyName 250 | } 251 | else { 252 | foreach ($Service in $ServiceName) { 253 | if ( Test-HashtableKey $Node 'Services' -NumberOfTabs 2) { 254 | if (Test-HashtableKey $Node['Services'] $Service -NumberOfTabs 3) { 255 | 256 | if ( Test-ServiceKey -Hashtable $Node -Service $Service -PropertyName $PropertyName ) { 257 | $Value += Resolve-HashtableProperty $Node['Services'][$Service] $PropertyName 258 | } 259 | elseif (Test-ApplicationKey -Hashtable $Node['Services'][$Service] -Application $Application) { 260 | $Value += Resolve-HashtableProperty $Node['Services'][$Service]['Applications'] $Application 261 | } 262 | } 263 | } 264 | } 265 | } 266 | 267 | if ($value.count -gt 0) { 268 | Write-Verbose "`t`tFound Node Value: $Value" 269 | } 270 | Write-Verbose "`tFinished checking Node $($Node.Name)" 271 | return $Value 272 | } 273 | 274 | function Assert-SiteOverride { 275 | [cmdletbinding()] 276 | param ( 277 | [System.Collections.Hashtable] 278 | $Node, 279 | [string[]] 280 | $ServiceName, 281 | [string] 282 | $Application, 283 | [string] 284 | $PropertyName, 285 | [switch] 286 | $AllowMultipleResults, 287 | [System.Collections.Hashtable] 288 | $ConfigurationData 289 | ) 290 | $Value = @() 291 | $Site = $Node.Location 292 | Write-Verbose "`tStarting to check Site $Site" 293 | if ((Test-HashtableKey $ConfigurationData 'SiteData' -NumberOfTabs 2) -and 294 | (Test-HashtableKey $ConfigurationData.SiteData $Site -NumberOfTabs 2) 295 | ) { 296 | if ( ($ServiceName.count -eq 0) -and 297 | (-not [string]::IsNullOrEmpty($Application)) -and 298 | (Test-ApplicationKey -Hashtable $ConfigurationData.SiteData[$Site] -Application $Application )) { 299 | $Value += Resolve-HashtableProperty $ConfigurationData.SiteData[$Site]['Applications'] $Application 300 | 301 | } 302 | elseif ( ($ServiceName.count -eq 0) -and 303 | (Test-HashtableKey $ConfigurationData.SiteData[$Site] $PropertyName ) ){ 304 | $Value += Resolve-HashtableProperty $ConfigurationData.SiteData[$Site] $PropertyName 305 | } 306 | else { 307 | foreach ($Service in $ServiceName) { 308 | if (Test-HashtableKey $ConfigurationData.SiteData[$Site] 'Services' -NumberOfTabs 2) { 309 | if (Test-HashtableKey $ConfigurationData.SiteData[$Site]['Services'] $Service -NumberOfTabs 3) { 310 | if (Test-ApplicationKey -Hashtable $ConfigurationData.SiteData[$Site]['Services'][$Service] -Application $Application) { 311 | $Value += Resolve-HashtableProperty $ConfigurationData.SiteData[$Site]['Services'][$Service]['Applications'] $Application 312 | } 313 | elseif ( Test-ServiceKey -Hashtable $ConfigurationData.SiteData[$Site] -Service $Service -PropertyName $PropertyName ) { 314 | $Value += Resolve-HashtableProperty $ConfigurationData.SiteData[$Site]['Services'][$Service] $PropertyName 315 | } 316 | } 317 | } 318 | } 319 | } 320 | } 321 | Write-Verbose "`tFinished checking Site $Site" 322 | return $Value 323 | } 324 | 325 | function Assert-GlobalSetting { 326 | [cmdletbinding()] 327 | param ( 328 | [System.Collections.Hashtable] 329 | $Node, 330 | [string[]] 331 | $ServiceName, 332 | [string] 333 | $Application, 334 | [string] 335 | $PropertyName, 336 | [switch] 337 | $AllowMultipleResults, 338 | [System.Collections.Hashtable] 339 | $ConfigurationData 340 | ) 341 | $Value = @() 342 | Write-Verbose "Bound parameters include:" 343 | foreach ($key in $PSBoundParameters.keys) { 344 | Write-Verbose "`t$key is $($PSBoundParameters[$key])" 345 | } 346 | Write-Verbose "`tStarting to check global settings" 347 | if ($ServiceName.count -eq 0) { 348 | if (-not [string]::IsNullOrEmpty($Application)) { 349 | if (Test-ApplicationKey -Hashtable $ConfigurationData -Application $Application) { 350 | $Value += Resolve-HashtableProperty $ConfigurationData['Applications'] $Application 351 | } 352 | } 353 | else { 354 | Write-Verbose "`t`tStarting to check Site: All" 355 | if (Test-HashtableKey $ConfigurationData 'SiteData' -NumberOfTabs 3) { 356 | if (Test-HashtableKey $ConfigurationData.SiteData 'All' -NumberOfTabs 4) { 357 | if (Test-HashtableKey $ConfigurationData.SiteData.All $PropertyName -NumberOfTabs 5) { 358 | $Value += Resolve-HashtableProperty $ConfigurationData.SiteData.All $PropertyName 359 | } 360 | } 361 | } 362 | } 363 | Write-Verbose "`t`tFinished checking Site: All" 364 | } 365 | else { 366 | foreach ($Service in $ServiceName) { 367 | Write-Verbose "`t`tStarting to check Service:$Service" 368 | $Found = $false 369 | if (Test-HashtableKey $ConfigurationData 'SiteData' -NumberOfTabs 3) { 370 | if (Test-HashtableKey $ConfigurationData.SiteData 'All' -NumberOfTabs 4) { 371 | if (Test-HashtableKey $ConfigurationData.SiteData.All 'Services' -NumberOfTabs 5) { 372 | if (Test-HashtableKey $ConfigurationData.SiteData.All.Services $Service -NumberOfTabs 6) { 373 | if (Test-ServiceKey -Hashtable $ConfigurationData.SiteData.All -Service $Service -PropertyName $PropertyName) { 374 | $Found = $true 375 | $Value += Resolve-HashtableProperty $ConfigurationData.SiteData.All.Services[$Service] $PropertyName 376 | } 377 | elseif (Test-ApplicationKey -Hashtable $ConfigurationData.SiteData.All.Services[$Service] -Application $Application) { 378 | $Found = $true 379 | $Value += Resolve-HashtableProperty $ConfigurationData.SiteData.All.Services[$Service]['Applications'] $Application 380 | } 381 | } 382 | } 383 | } 384 | } 385 | if ((-not $found) -and (Test-HashtableKey $ConfigurationData 'Services' -NumberOfTabs 3)) { 386 | if (Test-HashtableKey $ConfigurationData.Services $Service -NumberOfTabs 4) { 387 | if (Test-ServiceKey -Hashtable $ConfigurationData -Service $Service -PropertyName $PropertyName) { 388 | $Value += Resolve-HashtableProperty $ConfigurationData.Services[$Service] $PropertyName 389 | } 390 | elseif (Test-ApplicationKey -Hashtable $ConfigurationData.Services[$Service] -Application $Application ) { 391 | $Value += Resolve-HashtableProperty $ConfigurationData.Services[$Service]['Applications'] $Application 392 | } 393 | } 394 | } 395 | 396 | Write-Verbose "`t`tFinished checking Service:$Service" 397 | } 398 | } 399 | Write-Verbose "`tFound Global Value: $Value" 400 | return $Value 401 | } 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | -------------------------------------------------------------------------------- /Tooling/cDscDiagnostics/cDscDiagnostics.psm1.Tests.ps1: -------------------------------------------------------------------------------- 1 | Remove-Module [c]DscDiagnostics 2 | Import-Module $PSScriptRoot\cDscDiagnostics.psm1 3 | 4 | Describe "Trace-cDscOperation" { 5 | Context "does it call its internal functions" { 6 | Mock -ModuleName cDscDiagnostics Add-ClassTypes {} 7 | Mock -ModuleName cDscDiagnostics Trace-DscOperationInternal {} 8 | Mock -ModuleName cDscDiagnostics Log {} 9 | 10 | $result = Trace-cDscOperation -ComputerName $env:ComputerName; 11 | 12 | It "should call Add-ClassType" { 13 | Assert-MockCalled Add-ClassTypes -ModuleName cDscDiagnostics -Times 1 14 | } 15 | 16 | It "should call Trace-DscOperationInternal" { 17 | Assert-MockCalled Trace-DscOperationInternal -ModuleName cDscDiagnostics -Times 1 18 | } 19 | 20 | It "should call Log" { 21 | Assert-MockCalled Log -ModuleName cDscDiagnostics -Times 1 22 | } 23 | } 24 | } 25 | 26 | Describe "Add-ClassTypes" { 27 | Context "when its called" { 28 | Mock -ModuleName cDscDiagnostics Update-FormatData {} 29 | Mock -ModuleName cDscDiagnostics Trace-DscOperationInternal {} 30 | Mock -ModuleName cDscDiagnostics Log {} 31 | 32 | $result = Trace-cDscOperation -ComputerName $env:ComputerName; 33 | 34 | It "should have loaded it's event types" { 35 | { [Microsoft.PowerShell.cDscDiagnostics.EventType]::ANALYTIC } | Should Not Throw 36 | } 37 | 38 | It "should have loaded it's group events" { 39 | { [Microsoft.PowerShell.cDscDiagnostics.GroupedEvents] } | Should Not Throw 40 | } 41 | } 42 | } 43 | 44 | InModuleScope cDscDiagnostics { 45 | Describe 'Log' { 46 | It 'Should write verbosely' { 47 | $text = "Verbose Text" 48 | $verboseLog = Log $text -Verbose 4>&1 49 | $verboseLog | Should Be $text 50 | } 51 | 52 | It 'should write errors' { 53 | $text = "Error Text" 54 | $errorLog = Log $text -Error 2>&1 55 | $errorLog | Should Be $text 56 | } 57 | 58 | BeforeEach { 59 | $vPreference = $VerbosePreference; 60 | $ePreference = $ErrorActionPreference; 61 | 62 | $VerbosePreference = "Continue"; 63 | $ErrorActionPreference = "Continue"; 64 | } 65 | 66 | AfterEach { 67 | $VerbosePreference = $vPreference; 68 | $ErrorActionPreference = $ePreference; 69 | } 70 | } 71 | 72 | Describe 'Trace-DscOperationInternal' { 73 | Context 'SequenceID is passed' { 74 | Mock Log { } 75 | $result = Trace-DscOperationInternal -SequenceID 0; 76 | 77 | It 'should return null if SequenceID is less then 1' { 78 | $result | Should Be $null; 79 | } 80 | 81 | $date = Get-Date; 82 | $message = "Some Message"; 83 | # Choosing Application because we need /something/ here and we can't assume that the machine has run a DSC command. 84 | $event = Get-WinEvent -LogName Application -MaxEvents 1 85 | 86 | $traceOutput = New-Object PSObject -Property @{ 87 | Type = "Operational"; 88 | TimeCreated = $date; 89 | Message = $message; 90 | ComputerName = $env:ComputerName; 91 | SequenceID = 2; 92 | Event = $event; 93 | } 94 | 95 | Mock Get-SingleDscOperation { return @{ 96 | AllEvents = $traceOutput; 97 | ErrorEvents = "SomeEvent"; 98 | JobId = "54137c5a-f607-48a5-9311-d6e102c15f83"; 99 | } 100 | } 101 | 102 | Mock Test-DscEventLogStatus { } 103 | Mock Get-DscErrorMessage { return "Error Text" } 104 | 105 | $result = Trace-DscOperationInternal -SequenceID 2; 106 | 107 | It 'should do call Test-DscEventLogStatus' { 108 | Assert-MockCalled Test-DscEventLogStatus -Times 2 109 | } 110 | 111 | It 'should call Get-SingleDscOperation' { 112 | Assert-MockCalled Get-SingleDscOperation -Times 1 113 | } 114 | 115 | It 'should call Get-DscErrorMessage' { 116 | Assert-MockCalled Get-DscErrorMessage -Times 1 117 | } 118 | 119 | It 'should return the correct event type' { 120 | $result.EventType | Should Be "Operational"; 121 | } 122 | 123 | It 'should return the correct time' { 124 | $result.TimeCreated | Should Be $date; 125 | } 126 | 127 | It 'should return the correct message' { 128 | $result.Message | Should Be $message; 129 | } 130 | 131 | It 'should return the correct machine' { 132 | $result.ComputerName | Should Be $env:ComputerName; 133 | } 134 | 135 | It 'should return the correct SequenceID' { 136 | $result.SequenceID | Should Be 2; 137 | } 138 | 139 | It 'should return the correct Event' { 140 | $result.Event | Should Be $event; 141 | } 142 | } 143 | 144 | Context 'JobId Passed' { 145 | $date = Get-Date; 146 | $message = "Some Message"; 147 | # Choosing Application because we need /something/ here and we can't assume that the machine has run a DSC command. 148 | $event = Get-WinEvent -LogName Application -MaxEvents 1 149 | 150 | $traceOutput = New-Object PSObject -Property @{ 151 | Type = "Operational"; 152 | TimeCreated = $date; 153 | Message = $message; 154 | ComputerName = $env:ComputerName; 155 | SequenceID = 1; 156 | Event = $event; 157 | } 158 | 159 | Mock Get-SingleDscOperation { return @{ 160 | AllEvents = $traceOutput; 161 | ErrorEvents = "SomeEvent"; 162 | JobId = "54137c5a-f607-48a5-9311-d6e102c15f83"; 163 | } 164 | } 165 | 166 | Mock Test-DscEventLogStatus { } 167 | Mock Get-DscErrorMessage { return "Error Text" } 168 | 169 | $result = Trace-DscOperationInternal -SequenceID 1 -JobId "54137c5a-f607-48a5-9311-d6e102c15f83"; 170 | 171 | It 'should return the Job Id' { 172 | $result.JobId | Should Be "54137c5a-f607-48a5-9311-d6e102c15f83" 173 | } 174 | } 175 | 176 | Context 'Get-SingleDscOperation returns null' { 177 | Mock Test-DscEventLogStatus { } 178 | Mock Get-SingleDscOperation { } 179 | 180 | $result = Trace-DscOperationInternal -SequenceID 1 -JobId "54137c5a-f607-48a5-9311-d6e102c15f83"; 181 | 182 | It 'should return null' { 183 | $result | Should Be $null; 184 | } 185 | } 186 | 187 | # This way we don't cache anything while testing. 188 | AfterEach { 189 | Clear-DscDiagnosticsCache 190 | } 191 | } 192 | 193 | Describe 'Get-DscLatestJobId' { 194 | Context 'It has a Job to Return' { 195 | Mock Get-WinEvent { 196 | $value = @{"Value" = "{3BBB79B7-BD46-424C-9718-983C8C76D37E}"} 197 | $returnObject = @(@{Properties = @($value)}, [Environment]::NewLine) 198 | return @(@{Properties = @($value)}, [Environment]::NewLine) 199 | } 200 | 201 | $result = Get-DscLatestJobId; 202 | 203 | It 'should return the GUID' { 204 | $result | Should Be "{3BBB79B7-BD46-424C-9718-983C8C76D37E}" 205 | } 206 | } 207 | 208 | Context 'When it does not have a Job' { 209 | Mock Get-WinEvent { 210 | return $null 211 | } 212 | 213 | $result = Get-DscLatestJobId; 214 | 215 | It 'should return "NOJOBID"' { 216 | $result | Should Be "NOJOBID" 217 | } 218 | } 219 | 220 | AfterEach { 221 | Clear-DscDiagnosticsCache 222 | } 223 | } 224 | 225 | Describe 'Get-AllDscEvents' { 226 | Mock Get-WinEvent { 227 | Microsoft.PowerShell.Diagnostics\Get-WinEvent -LogName Application -MaxEvents 1 228 | } 229 | 230 | $result = Get-AllDscEvents; 231 | 232 | It 'should do call Get-WinEvent for each log passed' { 233 | Assert-MockCalled Get-WinEvent -Times 3 234 | $result.Count | Should Be 3 235 | } 236 | 237 | It 'should return something' { 238 | $result | Should Not Be $null; 239 | } 240 | } 241 | 242 | Describe 'Get-AllGroupedDscEvents' { 243 | Context 'Get-AllDscEvents returns null' { 244 | Mock Get-AllDscEvents {} 245 | Mock Get-DscLatestJobId {return "NOJOBID"} 246 | Mock Log {} 247 | $result = Get-AllGroupedDscEvents; 248 | 249 | It "should call Log" { 250 | Assert-MockCalled Log -Times 1 251 | } 252 | 253 | It 'should call Get-DscLatestJobId' { 254 | Assert-MockCalled Get-DscLatestJobId -Times 1 255 | } 256 | 257 | It 'should return null' { 258 | $result | Should Be $null 259 | } 260 | } 261 | 262 | Context 'Get-AllDscEvents returns events' { 263 | $value = @{"Value" = "{3BBB79B7-BD46-424C-9718-983C8C76D37E}"} 264 | Mock Get-AllDscEvents {return @{Properties = @($value)}} 265 | Mock Log {} 266 | Mock Get-DscLatestJobId { return 'MOCKEDJOBID' } 267 | $result = Get-AllGroupedDscEvents 268 | 269 | It 'should return a specific event' { 270 | $result.Name | Should Be "{3BBB79B7-BD46-424C-9718-983C8C76D37E}" 271 | } 272 | } 273 | 274 | Context 'Cached Events' { 275 | Mock Get-DscLatestJobId { return 'MOCKEDJOBID' } 276 | Mock Get-AllDscEvents {} 277 | 278 | $result = Get-AllGroupedDscEvents; 279 | 280 | It 'should return a specific event' { 281 | Assert-MockCalled Get-AllDscEvents -Scope It -Times 0 282 | $result.Name | Should Be "{3BBB79B7-BD46-424C-9718-983C8C76D37E}" 283 | } 284 | } 285 | } 286 | 287 | Describe 'Get-SingleDscOperation' { 288 | Context 'Get-AllGroupedDscEvents is empty' { 289 | Mock Get-AllGroupedDscEvents {} 290 | 291 | $result = Get-SingleDscOperation; 292 | 293 | It 'should return empty' { 294 | $result | Should BeNullorEmpty 295 | } 296 | } 297 | 298 | Context 'JobId is Passed but its not found' { 299 | Mock Log {} 300 | Mock Get-AllGroupedDscEvents {@{Name = "NOJOBID"}} 301 | $result = Get-SingleDscOperation -JobId 3BBB79B7-BD46-424C-9718-983C8C76D37E 302 | 303 | It 'should return empty' { 304 | $result | Should BeNullorEmpty 305 | } 306 | } 307 | 308 | Context 'JobId is passed and found' { 309 | Mock Log {} 310 | Mock Get-AllGroupedDscEvents { 311 | return New-Object PSObject -Property @{ 312 | Name = New-Object PSObject -Property @{ 313 | Guid = "3bbb79b7-bd46-424c-9718-983c8c76d37e" 314 | } 315 | 316 | Count = 1 317 | } 318 | } 319 | 320 | Mock Split-SingleDscGroupedRecord { $true } 321 | 322 | $result = Get-SingleDscOperation -JobId 3BBB79B7-BD46-424C-9718-983C8C76D37E 323 | 324 | It 'should have called Get-SingleDscOperation' { 325 | Assert-MockCalled Split-SingleDscGroupedRecord -Times 1 326 | $result | Should Be $true 327 | } 328 | } 329 | } 330 | 331 | Describe 'Split-SingleDscGroupedRecord' { 332 | Context 'Passed an bad DSC record' { 333 | $TimeCreated = Get-Date; 334 | $singleRecordInGroupedEvents = New-Object PSObject -Property @{ 335 | Group = New-Object PSObject -Property @{ 336 | Guid = "3bbb79b7-bd46-424c-9718-983c8c76d37e" 337 | TimeCreated = $TimeCreated; 338 | Level = 2; 339 | ContainerLog = "Microsoft-Windows-Dsc/Operational"; 340 | } 341 | Name = "{54137C5A-F607-48A5-9311-D6E102C15F83}" 342 | Count = 1 343 | } 344 | 345 | Mock Get-MessageFromEvent {return "Mocked Message"} 346 | 347 | $result = Split-SingleDscGroupedRecord -singleRecordInGroupedEvents $singleRecordInGroupedEvents -Index 0; 348 | 349 | It 'should have SequenceID match the index' { 350 | $result.SequenceID | Should Be 0 351 | } 352 | 353 | It 'should have ComputerName match this computer' { 354 | $result.ComputerName | Should Be $env:ComputerName 355 | } 356 | 357 | It 'should have a failure as the result' { 358 | $result.Result | Should Be "Failure" 359 | } 360 | 361 | It 'should have error as the type' { 362 | $result.AllEvents[0].Type | Should Be "ERROR" 363 | } 364 | 365 | It 'should have the mocked message' { 366 | $result.AllEvents[0].Message | Should Be "Mocked Message" 367 | } 368 | 369 | It 'should have the right JobId' { 370 | $result.JobId | Should Be "54137C5A-F607-48A5-9311-D6E102C15F83" 371 | } 372 | 373 | It 'should have the right count' { 374 | $result.NumberOfEvents | Should Be 1 375 | } 376 | } 377 | 378 | Context 'Passing a Warning' { 379 | $TimeCreated = Get-Date; 380 | $singleRecordInGroupedEvents = New-Object PSObject -Property @{ 381 | Group = New-Object PSObject -Property @{ 382 | Guid = "3bbb79b7-bd46-424c-9718-983c8c76d37e" 383 | TimeCreated = $TimeCreated; 384 | Level = 1; 385 | ContainerLog = "Microsoft-Windows-Dsc/Operational"; 386 | LevelDisplayName = "Warning"; 387 | } 388 | Name = "{54137C5A-F607-48A5-9311-D6E102C15F83}" 389 | } 390 | 391 | Mock Get-MessageFromEvent {return "Mocked Message"} 392 | 393 | $result = Split-SingleDscGroupedRecord -singleRecordInGroupedEvents $singleRecordInGroupedEvents -Index 0; 394 | 395 | it 'should find a warning event' { 396 | $result.WarningEvents | Should Not Be $null 397 | } 398 | } 399 | 400 | Context 'Passing an Operational Log' { 401 | $TimeCreated = Get-Date; 402 | $singleRecordInGroupedEvents = New-Object PSObject -Property @{ 403 | Group = New-Object PSObject -Property @{ 404 | Guid = "3bbb79b7-bd46-424c-9718-983c8c76d37e" 405 | TimeCreated = $TimeCreated; 406 | Level = 1; 407 | ContainerLog = "Microsoft-Windows-Dsc/operational"; 408 | LevelDisplayName = "Operational"; 409 | } 410 | Name = "{54137C5A-F607-48A5-9311-D6E102C15F83}" 411 | } 412 | 413 | Mock Get-MessageFromEvent {return "Mocked Message"} 414 | 415 | $result = Split-SingleDscGroupedRecord -singleRecordInGroupedEvents $singleRecordInGroupedEvents -Index 0; 416 | 417 | It 'should find the right type' { 418 | $result.AllEvents[0].Type | Should Be "OPERATIONAL" 419 | } 420 | 421 | It 'should find some operational events' { 422 | $result.OperationalEvents | Should Not Be $null 423 | } 424 | 425 | It 'should find some non-verbose events' { 426 | $result.NonVerboseEvents | Should Not Be $null 427 | } 428 | } 429 | 430 | Context 'Passing a Debug Log' { 431 | $TimeCreated = Get-Date; 432 | $singleRecordInGroupedEvents = New-Object PSObject -Property @{ 433 | Group = New-Object PSObject -Property @{ 434 | Guid = "3bbb79b7-bd46-424c-9718-983c8c76d37e" 435 | TimeCreated = $TimeCreated; 436 | Level = 1; 437 | ContainerLog = "Microsoft-Windows-Dsc/debug"; 438 | LevelDisplayName = "Debug"; 439 | } 440 | Name = "{54137C5A-F607-48A5-9311-D6E102C15F83}" 441 | } 442 | 443 | Mock Get-MessageFromEvent {return "Mocked Message"} 444 | 445 | $result = Split-SingleDscGroupedRecord -singleRecordInGroupedEvents $singleRecordInGroupedEvents -Index 0; 446 | 447 | It 'should find the right type' { 448 | $result.AllEvents[0].Type | Should Be "DEBUG" 449 | } 450 | 451 | It 'should find some debug events' { 452 | $result.DebugEvents | Should Not Be $null 453 | } 454 | } 455 | 456 | Context 'Passing a Verbose Log' { 457 | $TimeCreated = Get-Date; 458 | $singleRecordInGroupedEvents = New-Object PSObject -Property @{ 459 | Group = New-Object PSObject -Property @{ 460 | Guid = "3bbb79b7-bd46-424c-9718-983c8c76d37e" 461 | TimeCreated = $TimeCreated; 462 | Level = 1; 463 | ContainerLog = "Microsoft-Windows-Dsc/analytic"; 464 | LevelDisplayName = "analytic"; 465 | Id = 4100; 466 | } 467 | Name = "{54137C5A-F607-48A5-9311-D6E102C15F83}" 468 | } 469 | 470 | Mock Get-MessageFromEvent {return "Mocked Message"} 471 | 472 | $result = Split-SingleDscGroupedRecord -singleRecordInGroupedEvents $singleRecordInGroupedEvents -Index 0; 473 | 474 | It 'should find the right type' { 475 | $result.AllEvents[0].Type | Should Be "Verbose" 476 | } 477 | 478 | It 'should find some debug events' { 479 | $result.VerboseEvents | Should Not Be $null 480 | } 481 | } 482 | 483 | Context 'Passing an Analytic Log' { 484 | $TimeCreated = Get-Date; 485 | $singleRecordInGroupedEvents = New-Object PSObject -Property @{ 486 | Group = New-Object PSObject -Property @{ 487 | Guid = "3bbb79b7-bd46-424c-9718-983c8c76d37e" 488 | TimeCreated = $TimeCreated; 489 | Level = 1; 490 | ContainerLog = "Microsoft-Windows-Dsc/analytic"; 491 | LevelDisplayName = "analytic"; 492 | } 493 | Name = "{54137C5A-F607-48A5-9311-D6E102C15F83}" 494 | } 495 | 496 | Mock Get-MessageFromEvent {return "Mocked Message"} 497 | 498 | $result = Split-SingleDscGroupedRecord -singleRecordInGroupedEvents $singleRecordInGroupedEvents -Index 0; 499 | 500 | It 'should find the right type' { 501 | $result.AllEvents[0].Type | Should Be "Analytic" 502 | } 503 | 504 | It 'should find some analytic events' { 505 | $result.NonVerboseEvents | Should Not Be $null 506 | } 507 | } 508 | } 509 | 510 | Describe 'Get-MessageFromEvent' { 511 | Context 'For a non-verbose event' { 512 | $eventRecord = New-Object PSObject -Property @{ 513 | Message = [Environment]::NewLine + "Some Message"; 514 | } 515 | 516 | $result = Get-MessageFromEvent -EventRecord $eventRecord 517 | 518 | It 'should return the value' { 519 | $result | Should Be "Some Message" 520 | } 521 | } 522 | 523 | Context 'For a verbose event' { 524 | $verboseMessage = New-Object PSObject -Property @{Value = "Verbose Message"} 525 | $value = @([Environment]::NewLine, [Environment]::NewLine, $verboseMessage) 526 | $eventRecord = New-Object PSObject -Property @{ 527 | Message = [Environment]::NewLine + "Some Message"; 528 | Id = 4117 529 | Properties = $value; 530 | } 531 | 532 | $result = Get-MessageFromEvent -EventRecord $eventRecord -verboseType 533 | 534 | It 'should return the value' { 535 | $result | Should Be "Verbose Message" 536 | } 537 | } 538 | } 539 | 540 | Describe 'Get-DscErrorMessage' { 541 | Mock Get-SingleRelevantErrorMessage {return "Output Error Message"} 542 | $errorRecords = @{Id = "4131";} 543 | 544 | $result = Get-DscErrorMessage -ErrorRecords $errorRecords 545 | 546 | It 'should be the message' { 547 | $result | should be "Output Error Message " 548 | } 549 | } 550 | 551 | Describe 'Get-SingleRelevantErrorMessage' { 552 | Context 'Property Index is -1' { 553 | $value = @{Value = "Property"} 554 | $hash = @([Environment]::NewLine; $value) 555 | $errorEvent = @{Id = 4131; Properties = $hash} 556 | 557 | $result = Get-SingleRelevantErrorMessage -ErrorEvent $errorEvent; 558 | 559 | It 'should reutrn the correct string' { 560 | $result | should be "Property" 561 | } 562 | } 563 | 564 | Context 'Property Index is not -1' { 565 | Mock Get-MessageFromEvent {return "Mocked Message"} 566 | 567 | $value = @{Value = "Property"} 568 | $hash = @([Environment]::NewLine; $value) 569 | $errorEvent = @{Id = 4183; Properties = $hash} 570 | 571 | $result = Get-SingleRelevantErrorMessage -ErrorEvent $errorEvent; 572 | 573 | It 'should call Get-MessageFromEvent' { 574 | Assert-MockCalled Get-MessageFromEvent 575 | } 576 | 577 | It 'should reutrn the correct string' { 578 | $result | should be "Mocked Message" 579 | } 580 | } 581 | } 582 | 583 | Describe 'Get-DscOperationInternal' { 584 | Mock Get-AllGroupedDscEvents { return "GroupedEvents" } 585 | Mock Split-SingleDscGroupedRecord { return "singleOutputRecord" } 586 | 587 | $result = Get-DscOperationInternal -Newest 1; 588 | 589 | It 'should call Get-AllGroupedDscEvents' { 590 | Assert-MockCalled Get-AllGroupedDscEvents 591 | } 592 | 593 | It 'should loop over the events the correct amount of times' { 594 | Assert-MockCalled Split-SingleDscGroupedRecord -Times 1 595 | } 596 | 597 | It 'should return the singleOutputRecord' { 598 | $result | Should Be "singleOutputRecord" 599 | } 600 | } 601 | 602 | Describe 'Test-DscEventLogStatus' { 603 | Context 'Log is Enabled' { 604 | Mock Get-WinEvent {return @{IsEnabled = $true}} 605 | $result = Test-DscEventLogStatus 606 | 607 | It 'should return true' { 608 | $result | Should Be $true 609 | } 610 | } 611 | 612 | Context 'Log is Disabled and not enabled' { 613 | Mock Get-WinEvent {return @{IsEnabled = $false}} 614 | Mock Log {} 615 | Mock Read-Host {return "n"}; 616 | 617 | $result = Test-DscEventLogStatus 618 | 619 | It 'should return false' { 620 | $result | Should Be $false 621 | } 622 | 623 | It 'should call Log' { 624 | Assert-MockCalled Log 625 | } 626 | 627 | It 'should call Read-Host' { 628 | Assert-MockCalled Read-Host 629 | } 630 | } 631 | 632 | Context 'Log is Disabled and is enabled' { 633 | Mock Get-WinEvent {return @{IsEnabled = $false}} 634 | Mock Enable-DscEventLog {} 635 | Mock Read-Host {return "y"}; 636 | Mock Write-Host {}; 637 | 638 | $result = Test-DscEventLogStatus 639 | 640 | It 'should return false' { 641 | $result | Should Be $false 642 | } 643 | 644 | It 'should call Write-Host' { 645 | Assert-MockCalled Write-Host 646 | } 647 | 648 | It 'should call Read-Host' { 649 | Assert-MockCalled Read-Host 650 | } 651 | 652 | It 'should call Enable-DscEventLog' { 653 | Assert-MockCalled Enable-DscEventLog 654 | } 655 | } 656 | } 657 | } 658 | 659 | Describe "Get-cDscOperation" { 660 | Context "does it call its internal functions" { 661 | Mock -ModuleName cDscDiagnostics Add-ClassTypes {} 662 | Mock -ModuleName cDscDiagnostics Get-DscOperationInternal {} 663 | Mock -ModuleName cDscDiagnostics Log {} 664 | 665 | $result = Get-cDscOperation -ComputerName $env:ComputerName; 666 | 667 | It "should call Add-ClassType" { 668 | Assert-MockCalled Add-ClassTypes -ModuleName cDscDiagnostics -Times 1 669 | } 670 | 671 | It "should call Get-DscOperationInternal" { 672 | Assert-MockCalled Get-DscOperationInternal -ModuleName cDscDiagnostics -Times 1 673 | } 674 | 675 | It "should call Log" { 676 | Assert-MockCalled Log -ModuleName cDscDiagnostics -Times 1 677 | } 678 | } 679 | } --------------------------------------------------------------------------------