diff --git a/Module/Private/Module/ConvertTo-PowerShellParameter.ps1 b/Module/Private/Module/ConvertTo-PowerShellParameter.ps1 index c401f5d..c229f8f 100644 --- a/Module/Private/Module/ConvertTo-PowerShellParameter.ps1 +++ b/Module/Private/Module/ConvertTo-PowerShellParameter.ps1 @@ -40,6 +40,24 @@ Converts the Datum and Template configuration into PowerShell Script Parameters. Process { + # + # If the configuration is null, throw an error. + if ($null -eq $ConfigurationTemplates) { + throw "ConfigurationTemplates cannot be null" + } + + # + # If the DatumConfiguration is null, throw an error. + if (($null -eq $ConfigurationTemplates.DatumConfiguration) -or ($ConfigurationTemplates.DatumConfiguration.Count -eq 0)) { + throw "ConfigurationTemplates.DatumConfiguration cannot be null" + } + + # + # If the TemplateConfiguration is null, throw an error. + if (($null -eq $ConfigurationTemplates.TemplateConfiguration) -or ($ConfigurationTemplates.TemplateConfiguration.Count -eq 0)) { + throw "ConfigurationTemplates.TemplateConfiguration cannot be null" + } + # # NodeTemplateConfiguration items have higher precidence then automatic values. # However it's possible to define positions within the configuration. diff --git a/Module/Private/Module/Set-ModuleParameters.ps1 b/Module/Private/Module/Set-ModuleParameters.ps1 index 1716062..7b51c9b 100644 --- a/Module/Private/Module/Set-ModuleParameters.ps1 +++ b/Module/Private/Module/Set-ModuleParameters.ps1 @@ -46,7 +46,8 @@ $params = @{ PullServerRegistrationKey = $CliXML.PullServerRegistrationKey DSCPullServer = $CliXML.DSCPullServer DSCPullServerHTTP = $CliXML.DSCPullServerHTTP - ScriptRunnerURL = $CliXML.ScriptRunnerURL + ScriptRunnerURL = $CliXML.ScriptRunnerURL + CertificateThumbprint = $CliXML.CertificateThumbprint } # Load the Global Settings @@ -77,7 +78,10 @@ Set's Global Configuration paramters used by the SRDSC Module. $DSCPullServerHTTP, [Parameter(Mandatory)] [String] - $ScriptRunnerURL + $ScriptRunnerURL, + [Parameter(Mandatory)] + [String] + $CertificateThumbprint ) $Global:SRDSC = [PSCustomObject]@{ @@ -98,7 +102,8 @@ Set's Global Configuration paramters used by the SRDSC Module. DSCPullServerMOFPath = 'C$\Program Files\WindowsPowerShell\DscService\Configuration\' DSCPullServerResourceModules = 'C$\Program Files\WindowsPowerShell\DscService\Modules\' DSCPullServerWebAddress = '{0}://{1}:8080' -f $DSCPullServerHTTP, $DSCPullServer - PullServerRegistrationKey = $PullServerRegistrationKey + PullServerRegistrationKey = $PullServerRegistrationKey + CertificateThumbprint = $CertificateThumbprint } DatumModule = [PSCustomObject]@{ diff --git a/Module/Public/Add-SRDSCNode.ps1 b/Module/Public/Add-SRDSCNode.ps1 index 3705c46..e8bad6c 100644 --- a/Module/Public/Add-SRDSCNode.ps1 +++ b/Module/Public/Add-SRDSCNode.ps1 @@ -55,17 +55,18 @@ function Add-SRDSCNode { Write-Host "[Add-SRDSCNode] PowerShell Remoting to $NodeName to Register LCM to $DSCPullServer" $RegistrationKey = $Global:SRDSC.DSCPullServer.PullServerRegistrationKey - + $CertificateByteArray = Get-Content -LiteralPath "{0}\PowerShell\SRDSC\PullServer.cer" -f $Env:ProgramData -Raw -Encoding Byte + # Load the DSC Server Configuration Data $invokeCommandParams = @{ - ArgumentList = $DSCPullServer,$Force,$RegistrationKey,$UseConfigurationIDs + ArgumentList = $DSCPullServer,$Force,$RegistrationKey,$UseConfigurationIDs,$CertificateByteArray ComputerName = $NodeName ErrorAction = 'Stop' } $NodeDSCLCMConfiguration = Invoke-Command @invokeCommandParams -ScriptBlock { - param($DSCPullServer, $Force, $RegistrationKey, $UseConfigurationIDs) + param($DSCPullServer, $Force, $RegistrationKey, $UseConfigurationIDs, $CertificateByteArray) # # Functions @@ -107,7 +108,27 @@ function Add-SRDSCNode { return Get-DscLocalConfigurationManager } - + # + # Install the DSC Certificate + # + + $certificateLocation = 'C:\Windows\Temp\SRDSCClientCertificate.cer' + $certificateStore = 'Cert:\LocalMachine\Root' + + # Remove any existing certificate at the specified location + Remove-Item -LiteralPath $certificateLocation -Force -ErrorAction SilentlyContinue + + # Save the certificate to the file system + [IO.File]::WriteAllBytes($certificateLocation, $CertificateByteArray) + + # Remove any existing certificates with matching subject name from the root store + Get-ChildItem $certificateStore -Recurse | + Where-Object {$_.Subject -like "*DSC.$ENV:USERDNSDOMAIN"} | + Remove-Item -Force -Confirm:$false + + # Import the certificate into the root store + Import-Certificate -FilePath $certificateLocation -CertStoreLocation $certificateStore + # # Compile the DSC Resource # @@ -148,7 +169,7 @@ function Add-SRDSCNode { [DSCLocalConfigurationManager()] configuration PullClientConfigNames { - + Node localhost { @@ -181,24 +202,21 @@ function Add-SRDSCNode { if ($UseConfigurationIDs.IsPresent) { Write-Host "[Add-SRDSCNode] Writing ConfigurationID of Node as [SecureString]" - - # - # The LCM Configuration is needed to register the ConfigurationID. - # This is used by the datum configuration to rename the mof files + + # Import existing node registrations from file, if any $DatumLCMConfiguration = @() - if (Test-Path -LiteralPath $Global:SRDSC.DatumModule.NodeRegistrationFile) { - $NodeRegistrationFile += Import-Clixml -LiteralPath $Global:SRDSC.DatumModule.NodeRegistrationFile - # Filter out the existing node node. This enable rewrites - $DatumLCMConfiguration = @() - $DatumLCMConfiguration += $NodeRegistrationFile | Where-Object {$_.NodeName -ne $NodeName} + $NodeRegistrationFile = Import-Clixml -LiteralPath $Global:SRDSC.DatumModule.NodeRegistrationFile + # Filter out the existing node to enable rewrites + $DatumLCMConfiguration = $NodeRegistrationFile | Where-Object {$_.NodeName -ne $NodeName} } - + + # Add the new node registration to the configuration list $DatumLCMConfiguration += [PSCustomObject]@{ NodeName = $NodeName ConfigurationID = [String]$NodeDSCLCMConfiguration.ConfigurationID | ConvertTo-SecureString -AsPlainText -Force } - + # Export it again $DatumLCMConfiguration | Export-Clixml -LiteralPath $Global:SRDSC.DatumModule.NodeRegistrationFile diff --git a/Module/Public/Initialize-SRDSC.ps1 b/Module/Public/Initialize-SRDSC.ps1 index 30929ea..b121e84 100644 --- a/Module/Public/Initialize-SRDSC.ps1 +++ b/Module/Public/Initialize-SRDSC.ps1 @@ -161,29 +161,9 @@ Onboarding script to install DSC Pull Server, Datum, and ScriptRunner Scripts. # Create Configuration file to store the datum module information $ConfigurationPath = "{0}\PowerShell\SRDSC\Configuration.clixml" -f $Env:ProgramData + $ClientCertificatePath = "{0}\PowerShell\SRDSC\PullServer.cer" -f $Env:ProgramData $ConfigurationParentPath = Split-Path $ConfigurationPath -Parent - # - # Set the Global Vars - - $SRConfiguration = @{ - DatumModulePath = $DatumModulePath - ScriptRunnerModulePath = Split-Path (Get-Module SRDSC).Path -Parent - ScriptRunnerServerPath = $ScriptRunnerServerPath - PullServerRegistrationKey = [guid]::newGuid().Guid - DSCPullServer = "DSC.{0}" -f $ComputerDomainName - DSCPullServerHTTP = $( - if ($UseSelfSignedCertificate.IsPresent -or $PFXCertificatePath) { - 'https' - } else { - 'http' - } - ) - ScriptRunnerURL = $ScriptRunnerURL - } - - Set-ModuleParameters @SRConfiguration - # # Load SSL Certificates @@ -211,6 +191,7 @@ Onboarding script to install DSC Pull Server, Datum, and ScriptRunner Scripts. # # If the SSL Certificate Path parameter was specified, import the cert + if ($PFXCertificatePath) { # @@ -225,42 +206,60 @@ Onboarding script to install DSC Pull Server, Datum, and ScriptRunner Scripts. $certificate = Import-PfxCertificate @params } - $xDscPullServerRegistrationParams = @{ + # + # Set the Global Vars - NodeName = 'localhost' - - xDscWebServiceRegistrationParams = @{ + $SRConfiguration = @{ + DatumModulePath = $DatumModulePath + ScriptRunnerModulePath = Split-Path (Get-Module SRDSC).Path -Parent + ScriptRunnerServerPath = $ScriptRunnerServerPath + PullServerRegistrationKey = [guid]::newGuid().Guid + DSCPullServer = "DSC.{0}" -f $ComputerDomainName + DSCPullServerHTTP = $( + if ($UseSelfSignedCertificate.IsPresent -or $PFXCertificatePath) { + 'https' + } else { + 'http' + } + ) + ScriptRunnerURL = $ScriptRunnerURL + CertificateThumbPrint = $certificate.Thumbprint + } - RegistrationKey = $SRConfiguration.PullServerRegistrationKey - WebServerFilePath = $PullWebServerPath - CertificateThumbPrint = $certificate.Thumbprint + Set-ModuleParameters @SRConfiguration - } + # + # Define a hashtable containing parameters for registering the local node with a DSC pull server + $xDscPullServerRegistrationParams = @{ + NodeName = 'localhost' # Specify the name of the local node to register + xDscWebServiceRegistrationParams = @{ + RegistrationKey = $SRConfiguration.PullServerRegistrationKey # Specify the registration key for the pull server + WebServerFilePath = $PullWebServerPath # Specify the path to the pull server web service endpoint + CertificateThumbPrint = $certificate.Thumbprint # Specify the thumbprint of the certificate to use for authentication + } xDscDatumModuleRegistrationParams = @{ - - DatumModulePath = $Global:SRDSC.DatumModule.DatumModulePath - DatumModuleTemplatePath = "{0}\{1}" -f $Global:SRDSC.DatumModule.DatumTemplates, (Split-Path $Global:SRDSC.ScriptRunner.NodeTemplateFile -Leaf) - SRDSCTemplateFile = $Global:SRDSC.ScriptRunner.NodeTemplateFile - + DatumModulePath = $Global:SRDSC.DatumModule.DatumModulePath # Specify the path to the datum module used by the script runner + DatumModuleTemplatePath = "{0}\{1}" -f $Global:SRDSC.DatumModule.DatumTemplates, (Split-Path $Global:SRDSC.ScriptRunner.NodeTemplateFile -Leaf) # Specify the path to the datum module template used by the script runner + SRDSCTemplateFile = $Global:SRDSC.ScriptRunner.NodeTemplateFile # Specify the path to the script runner node template file } - xDscSRDSCModuleRegistrationParams = @{ - ConfigurationParentPath = $ConfigurationParentPath - ScriptRunnerDSCRepository = $Global:SRDSC.ScriptRunner.ScriptRunnerDSCRepository + ConfigurationParentPath = $ConfigurationParentPath # Specify the path to the parent configuration directory for the script runner + ScriptRunnerDSCRepository = $Global:SRDSC.ScriptRunner.ScriptRunnerDSCRepository # Specify the path to the script runner DSC repository Files = @( - "{0}\Template\Publish-SRAction.ps1" -f $ModuleDirectory - "{0}\Template\Start-SRDSC.ps1" -f $ModuleDirectory - "{0}\Template\New-VirtualMachine.ps1" -f $ModuleDirectory + "{0}\Template\Publish-SRAction.ps1" -f $ModuleDirectory # Specify the path to the Publish-SRAction.ps1 script used by the script runner + "{0}\Template\Start-SRDSC.ps1" -f $ModuleDirectory # Specify the path to the Start-SRDSC.ps1 script used by the script runner + "{0}\Template\New-VirtualMachine.ps1" -f $ModuleDirectory # Specify the path to the New-VirtualMachine.ps1 script used by the script runner ) } - - OutputPath = 'C:\Windows\Temp\' - + OutputPath = 'C:\Windows\Temp\' # Specify the output path for the registration files } - + + # Register the local node with a DSC pull server using the parameters defined in the $xDscPullServerRegistrationParams hashtable xDscPullServerRegistration @xDscPullServerRegistrationParams - Start-DscConfiguration -Path 'C:\Windows\Temp' -Wait -Verbose -Force + + # Start a DSC configuration at the specified path, wait for it to complete, and output verbose messages + Start-DscConfiguration -Path 'C:\Windows\Temp' -Wait -Verbose -Force # # Use PowerShell Remoting and Invoke-DSCResource to create an C-NAME @@ -298,6 +297,13 @@ Onboarding script to install DSC Pull Server, Datum, and ScriptRunner Scripts. # Export the Configuration ([PSCustomObject]$SRConfiguration) | Export-Clixml -LiteralPath $ConfigurationPath + # + # Export the Public Certificate to ProgramData\PowerShell\SRDSC + # This will be used to onboard nodes into the DSC Pull Server + + (Get-ChildItem Cert:\LocalMachine\ -Recurse | Where-Object {$_.Subject -like ('*DSC.{0}*' -f $ENV:USERDNSDOMAIN)})[0] | + Export-Certificate -Force -FilePath $ClientCertificatePath + # # Clone the DSCWorkshop PowerShell Module (contains Datum) Write-Warning "[Initialize-SRDSC] Installing DSCWorkshop:" diff --git a/Tests/Private/ConvertTo-PowerShellParameter.tests.ps1 b/Tests/Private/ConvertTo-PowerShellParameter.tests.ps1 index f1a45c2..29e6568 100644 --- a/Tests/Private/ConvertTo-PowerShellParameter.tests.ps1 +++ b/Tests/Private/ConvertTo-PowerShellParameter.tests.ps1 @@ -1,104 +1,149 @@ +#This is a PowerShell script that tests the ConvertTo-PowerShellParameter function. +#The script uses the Pester testing framework to define test cases for the function. Describe "Testing ConvertTo-PowerShellParameter" { - it "Should returns an empty string when no input is provided" { - - $configurationTemplates = @() - $result = ConvertTo-PowerShellParameter -ConfigurationTemplates $configurationTemplates - $result | Should -Be "" - - } - - it "Should generate the expected Parameters for a given input." { - - $configurationTemplates = @{ - DatumConfiguration = @( - @{ - ParameterName = "NodeName" - ParameterValues = @("node1", "node2") - IsOverwritten = $false - } - ) - TemplateConfiguration = @( - @{ - ParameterName = "VMName" - YAMLPath = "name" - ParameterExpression = "" - }, - @{ - ParameterName = "VMSize" - YAMLPath = "size" - ParameterExpression = "[ValidateNotNullOrEmpty()]" - } - ) - } - $formattedDatumConfig = New-Object PSObject -Property $configurationTemplates - $expectedResult = Import-MockData -CommandName 'ConvertTo-PowerShellParameter.test.2' - - $result = ConvertTo-PowerShellParameter -ConfigurationTemplates $formattedDatumConfig - $result | Should -Be $expectedResult - - } - - it "Test case checks if the function correctly handles duplicate parameters in the input." { - - $configurationTemplates = @{ - DatumConfiguration = @( - @{ - ParameterName = "NodeName" - ParameterValues = @("node1", "node2") - IsOverwritten = $false - } - ) - TemplateConfiguration = @( - @{ - ParameterName = "VMName" - YAMLPath = "name" - ParameterExpression = "" - }, - @{ - ParameterName = "NodeName" - YAMLPath = "name" - ParameterExpression = "" - } - ) + Context "when given a valid input" { + + it "should Generate the expected Parameters for a given input." { + + $configurationTemplates = @{ + DatumConfiguration = @( + @{ + ParameterName = "NodeName" + ParameterValues = @("node1", "node2") + IsOverwritten = $false + } + ) + TemplateConfiguration = @( + @{ + ParameterName = "VMName" + YAMLPath = "name" + ParameterExpression = "" + }, + @{ + ParameterName = "VMSize" + YAMLPath = "size" + ParameterExpression = "[ValidateNotNullOrEmpty()]" + } + ) + } + $formattedDatumConfig = New-Object PSObject -Property $configurationTemplates + $expectedResult = Import-MockData -CommandName 'ConvertTo-PowerShellParameter.test.2' + + $result = ConvertTo-PowerShellParameter -ConfigurationTemplates $formattedDatumConfig + $result | Should -Be $expectedResult + } - $formattedDatumConfig = New-Object PSObject -Property $configurationTemplates - $expectedResult = Import-MockData -CommandName 'ConvertTo-PowerShellParameter.test.3' + - $result = ConvertTo-PowerShellParameter -ConfigurationTemplates $formattedDatumConfig - $result | Should -be $expectedResult + it "should handle duplicate parameters in the input." { + + $configurationTemplates = @{ + DatumConfiguration = @( + @{ + ParameterName = "NodeName" + ParameterValues = @("node1", "node2") + IsOverwritten = $false + } + ) + TemplateConfiguration = @( + @{ + ParameterName = "VMName" + YAMLPath = "name" + ParameterExpression = "" + }, + @{ + ParameterName = "NodeName" + YAMLPath = "name" + ParameterExpression = "" + } + ) + } + $formattedDatumConfig = New-Object PSObject -Property $configurationTemplates + $expectedResult = Import-MockData -CommandName 'ConvertTo-PowerShellParameter.test.3' + + $result = ConvertTo-PowerShellParameter -ConfigurationTemplates $formattedDatumConfig + $result | Should -be $expectedResult + + } + + it "should correctly include custom validation expressions for parameters that have them" { + + $configurationTemplates = @{ + DatumConfiguration = @( + @{ + ParameterName = "NodeName" + ParameterValues = @("node1", "node2") + IsOverwritten = $false + } + ) + TemplateConfiguration = @( + @{ + ParameterName = "VMName" + YAMLPath = "name" + ParameterExpression = "" + }, + @{ + ParameterName = "VMSize" + YAMLPath = "size" + ParameterExpression = "[ValidatePattern('Standard_[A-Z]+')]" + } + ) + } + $formattedDatumConfig = New-Object PSObject -Property $configurationTemplates + $expectedResult = Import-MockData -CommandName 'ConvertTo-PowerShellParameter.test.4' + + $result = ConvertTo-PowerShellParameter -ConfigurationTemplates $formattedDatumConfig + $result | Should -Be $expectedResult + + } } - - it "This test case checks if the function correctly includes custom validation expressions for parameters that have them" { - - $configurationTemplates = @{ - DatumConfiguration = @( - @{ - ParameterName = "NodeName" - ParameterValues = @("node1", "node2") - IsOverwritten = $false - } - ) - TemplateConfiguration = @( - @{ - ParameterName = "VMName" - YAMLPath = "name" - ParameterExpression = "" - }, - @{ - ParameterName = "VMSize" - YAMLPath = "size" - ParameterExpression = "[ValidatePattern('Standard_[A-Z]+')]" - } - ) + + Context "when given invalid input" { + + It "throws an error when ConfigurationTemplates is null" { + { ConvertTo-PowerShellParameter -ConfigurationTemplates $null } | Should -Throw } - $formattedDatumConfig = New-Object PSObject -Property $configurationTemplates - $expectedResult = Import-MockData -CommandName 'ConvertTo-PowerShellParameter.test.4' - $result = ConvertTo-PowerShellParameter -ConfigurationTemplates $formattedDatumConfig - $result | Should -Be $expectedResult + It "throws an error when DatumConfiguration contains an empty array" { + + $configurationTemplates = @{ + DatumConfiguration = @() + TemplateConfiguration = @('value1', 'value2') + } + + { ConvertTo-PowerShellParameter -ConfigurationTemplates $configurationTemplates } | Should -Throw + } + It "throws an error when TemplateConfiguration contains an empty array" { + + $configurationTemplates = @{ + DatumConfiguration = @('value1', 'value2') + TemplateConfiguration = @() + } + + { ConvertTo-PowerShellParameter -ConfigurationTemplates $configurationTemplates } | Should -Throw + } + + It "throws an error when DatumConfiguration is null" { + + $configurationTemplates = @{ + DatumConfiguration = $null + TemplateConfiguration = 'value1' + } + + { ConvertTo-PowerShellParameter -ConfigurationTemplates $configurationTemplates } | Should -Throw + } + + It "throws an error when TemplateConfiguration is null" { + $configurationTemplates = @{ + DatumConfiguration = 'value1' + TemplateConfiguration = $null + } + + { ConvertTo-PowerShellParameter -ConfigurationTemplates $configurationTemplates } | Should -Throw + } } -} \ No newline at end of file +} diff --git a/Tests/Private/Set-ModuleParameters.tests.ps1 b/Tests/Private/Set-ModuleParameters.tests.ps1 index 3e5ad46..d9336c3 100644 --- a/Tests/Private/Set-ModuleParameters.tests.ps1 +++ b/Tests/Private/Set-ModuleParameters.tests.ps1 @@ -20,7 +20,8 @@ Describe "Testing Set-Module Parameters" { PullServerRegistrationKey = 'MOCK' DSCPullServer = 'MOCK' DSCPullServerHTTP = 'https' - ScriptRunnerURL = 'MOCK' + ScriptRunnerURL = 'MOCK' + CertificateThumbPrint = 'MOCK' } # @@ -41,6 +42,7 @@ Describe "Testing Set-Module Parameters" { $Global:SRDSC.DSCPullServer.DSCPullServerResourceModules | Should -Not -BeNullorEmpty $Global:SRDSC.DSCPullServer.DSCPullServerWebAddress | Should -Not -BeNullorEmpty $Global:SRDSC.DSCPullServer.PullServerRegistrationKey | Should -Not -BeNullorEmpty + $Global:SRDSC.DSCPullServer.CertificateThumbPrint | Should -Not -BeNullOrEmpty $Global:SRDSC.DatumModule.DatumModulePath | Should -Not -BeNullOrEmpty $Global:SRDSC.DatumModule.DatumTemplates | Should -Not -BeNullOrEmpty diff --git a/_Build/BuildVersion.txt b/_Build/BuildVersion.txt index 156b09b..6bd5f84 100644 --- a/_Build/BuildVersion.txt +++ b/_Build/BuildVersion.txt @@ -1,4 +1,5 @@ 0.0.1-alpha 0.0.2-alpha 0.0.3-alpha -0.0.4-alpha \ No newline at end of file +0.0.4-alpha +0.0.5-alpha \ No newline at end of file