DevOpsKitDsc.psm1
# # DevOps Kit for Desired State Configuration # # Import helper classes if (!$PSVersionTable.PSEdition -or $PSVersionTable.PSEdition -eq "Desktop") { Import-Module -Name "$PSScriptRoot/bin/Debug/net451/publish/DevOpsKitDsc.dll" | Out-Null } else { Import-Module -Name "$PSScriptRoot/bin/Debug/netstandard1.6/publish/DevOpsKitDsc.dll" | Out-Null } # # Localization # $LocalizedData = data { } Import-LocalizedData -BindingVariable LocalizedData -FileName 'DevOpsKitDsc.Resources.psd1' -ErrorAction SilentlyContinue; # # Public functions # #region Public functions # Bootstrap a DSC node with an encryption certificate and modules function Register-DOKDscNode { [CmdletBinding()] param ( [Parameter(Mandatory = $False)] [String[]]$InstanceName, [Parameter(Mandatory = $False)] [Alias('Path')] [String]$WorkspacePath = $PWD ) begin { Write-Verbose -Message "[DOKDsc] BEGIN::"; } process { if (!(Test-Path -Path $WorkspacePath)) { return; } # Get workspace settings $setting = Import-DOKDscWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; $nodePath = GetWorkspacePath -WorkspacePath $WorkspacePath -Path $setting.Options.NodePath -Verbose:$VerbosePreference; CreatePath -Path $nodePath; # Import node data $nodeData = ImportNodeData -NodePath $nodePath -InstanceName $InstanceName -Verbose:$VerbosePreference; if ($Null -eq $nodeData -or $nodeData.Length -eq 0) { Write-Error -Message $LocalizedData.ErrorMissingNodeData -Category ObjectNotFound -TargetObject $nodePath -ErrorAction Stop; } foreach ($node in $nodeData) { # Merge certificate information into node data MergeNodeCertificate -InputObject $node -Path $nodePath -InstanceName $node.InstanceName -Verbose:$VerbosePreference; # Register the node RegisterNode -Node $node -OutputPath $nodePath -Verbose:$VerbosePreference; # Copy required modules # CopyModules -Session $session -Path ''; } } end { Write-Verbose -Message "[DOKDsc] END::"; } } function Import-DOKDscNodeConfiguration { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $False)] [String[]]$InstanceName, [Parameter(Mandatory = $False)] [Alias('Path')] [String]$WorkspacePath = $PWD ) begin { Write-Verbose -Message "[DOKDsc] BEGIN::"; } process { if (!(Test-Path -Path $WorkspacePath)) { Write-Error -Message ($LocalizedData.WorkspacePathDoesNotExist) -Category ObjectNotFound -TargetObject $WorkspacePath -ErrorAction Stop; } # Get workspace settings $setting = Import-DOKDscWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; $nodePath = GetWorkspacePath -WorkspacePath $WorkspacePath -Path $setting.Options.NodePath -Verbose:$VerbosePreference; # Import node data $nodeData = ImportNodeData -NodePath $nodePath -InstanceName $InstanceName -Verbose:$VerbosePreference; if ($Null -eq $nodeData -or $nodeData.Length -eq 0) { Write-Error -Message ($LocalizedData.ErrorMissingNodeData -f $WorkspacePath) -Category ObjectNotFound -ErrorAction Stop; } foreach ($node in $nodeData) { # Merge certificate information into node data MergeNodeCertificate -InputObject $node -Path $nodePath -InstanceName $node.InstanceName -Verbose:$VerbosePreference; $node.ConfigurationData; } } end { Write-Verbose -Message "[DOKDsc] END::"; } } function Get-DOKDscCollection { [CmdletBinding(DefaultParameterSetName = 'Path')] [OutputType([DevOpsKitDsc.Workspace.Collection])] param ( [Parameter(Mandatory = $False, ParameterSetName = 'Path')] [String]$WorkspacePath = $PWD, [Parameter(Mandatory = $True, ParameterSetName = 'Setting')] [DevOpsKitDsc.Workspace.WorkspaceSetting]$Workspace, [Parameter(Mandatory = $False)] [String]$Name ) process { $setting = $Workspace; if ($PSCmdlet.ParameterSetName -eq 'Path') { # Get workspace settings $setting = Import-DOKDscWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; } $collections = $setting.Collections | Where-Object -FilterScript { (!$PSBoundParameters.ContainsKey('Name') -or $Name -contains $_.Name) }; return $collections; } } function New-DOKDscCollection { [CmdletBinding(DefaultParameterSetName = 'Path', SupportsShouldProcess = $True)] [OutputType([DevOpsKitDsc.Workspace.Collection])] param ( [Parameter(Mandatory = $False, ParameterSetName = 'Path')] [String]$WorkspacePath = $PWD, [Parameter(Mandatory = $True, ParameterSetName = 'Workspace')] [DevOpsKitDsc.Workspace.WorkspaceSetting]$Workspace, [Parameter(Position = 0, Mandatory = $True)] [String]$Name, [Parameter(Position = 1, Mandatory = $False)] [String]$Path ) process { # Get workspace settings $setting = Import-DOKDscWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; $filteredConfigurations = $setting.Collections | Where-Object -FilterScript { ($Name -eq $_.Name) }; if ($Null -ne $filteredConfigurations) { Write-Error -Message $LocalizedData.ConfigurationAlreadyExists -ErrorAction Stop; } [String]$configurationPath = GetWorkspacePath -WorkspacePath $WorkspacePath -Path $Path; # Use default configuration path if not specified if ([String]::IsNullOrEmpty($Path)) { $configurationPath = GetDefaultConfigurationPath -WorkspacePath $WorkspacePath -Setting $setting -ConfigurationName $Name -Verbose:$VerbosePreference; } # Check of the configuration script already exists if (!(Test-Path -Path $configurationPath)) { if ($PSCmdlet.ShouldProcess($LocalizedData.CreatingFromTemplate, $configurationPath)) { # Create a configuration from a template CopyTemplate -Name 'NewConfiguration.ps1' -Path $configurationPath -Verbose:$VerbosePreference; } } $relativePath = GetWorkspacePath -WorkspacePath $WorkspacePath -Path $configurationPath -Relative; $c = New-Object -TypeName DevOpsKitDsc.Workspace.Collection -Property @{ Name = $Name; Path = $relativePath; }; $setting.Collections.Add($c); if ($PSCmdlet.ShouldProcess($LocalizedData.WritingWorkspaceSettings, $WorkspacePath)) { WriteWorkspaceSetting -WorkspacePath $WorkspacePath -InputObject $setting -Verbose:$VerbosePreference; } return $c; } } function Publish-DOKDscCollection { [CmdletBinding()] [OutputType([void])] param ( [Parameter(Mandatory = $False)] [String[]]$Name, [Parameter(Mandatory = $False)] [Alias('paths')] [String]$WorkspacePath = $PWD ) begin { $dokOperation = 'Publish'; Write-Verbose -Message "[DOKDsc][$dokOperation] BEGIN::"; } process { # Get workspace settings $setting = Import-DOKDscWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; Write-Verbose -Message "[DOKDsc][$dokOperation] -- Using Collection: $Name"; # Filter collections by name as required $collections = $setting.Collections | Where-Object -FilterScript { !$PSBoundParameters.ContainsKey('Name') -or ($Name -contains $_.Name) }; # Process each matching collection foreach ($collection in $collections) { $outputPath = $setting.Options.OutputPath; $publishParams = @{ OutputPath = $outputPath; Name = $collection.Name; }; PublishConfiguration @publishParams; } } end { Write-Verbose -Message "[DOKDsc][$dokOperation] END::"; } } # Generate a unique .mof configuration document function Invoke-DOKDscBuild { [CmdletBinding()] param ( [Parameter(Mandatory = $False)] [String]$Name, [Parameter(Mandatory = $False)] [String[]]$InstanceName, [Parameter(Mandatory = $False)] [Alias('Path')] [String]$WorkspacePath = $PWD, [Parameter(Mandatory = $False)] [Object]$ConfigurationData, [Parameter(Mandatory = $False)] [System.Collections.IDictionary]$Parameters # [Parameter(Mandatory = $False)] # [Switch]$Wait = $False ) begin { $dokOperation = 'Deploy'; Write-Verbose -Message "[DOKDsc][$dokOperation] BEGIN::"; } process { # Get workspace settings $setting = Import-DOKDscWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; Write-Verbose -Message "[DOKDsc][$dokOperation] -- Using Collection: $Name"; Write-Verbose -Message "[DOKDsc][$dokOperation] -- Using BuildPath: $outputPath"; Write-Verbose -Message "[DOKDsc][$dokOperation] -- Using NodePath: $($setting.Options.NodePath)"; # Write-Verbose -Message "[DOKDsc][$dokOperation] -- Using SourcePath: $($setting.Workspace.SourcePath)"; Write-Verbose -Message "[DOKDsc][$dokOperation] -- Using ModulePath: $($setting.Options.ModulePath)"; # # Check if the path exists # if (!(Test-Path -Path $nodePath)) { # # No node data to process # return; # } $configFilterParams = @{ Workspace = $setting; }; if ($PSBoundParameters.ContainsKey($configFilterParams)) { $configFilterParams['Name'] = $Name; } $collections = Get-DOKDscCollection @configFilterParams -Verbose:$VerbosePreference; # Process each environment foreach ($collection in $collections) { # Get the output path $outputPath = GetWorkspacePath -WorkspacePath $WorkspacePath -Path $setting.Options.OutputPath -ChildPath $collection.Name; # Ensure that the output path exists $outputPath = CreatePath -Path $outputPath -PassThru -Verbose:$VerbosePreference; $sourcePath = (Get-Item -Path $collection.Path).FullName; $nodePath = $collection.Nodes; if ($Null -ne $nodePath) { # Import node data $nodeData = ImportNodeData -NodePath $nodePath -InstanceName $InstanceName -Verbose:$VerbosePreference; foreach ($node in $nodeData) { Write-Verbose -Message "[DOKDsc][$dokOperation] -- Processing node: $($node.InstanceName)"; try { # Merge certificate information into node data MergeNodeCertificate -InputObject $node -Path $node.BaseDirectory -InstanceName $node.InstanceName -Verbose:$VerbosePreference; MergeConfiguration -InputObject $node -Collection $collection -Verbose:$VerbosePreference; # Create job parameters $jobParams = New-Object -TypeName PSObject -Property @{ ConfigurationName = $configuration.Name; ConfigurationData = $node.ConfigurationData; Parameters = $Parameters; Path = $sourcePath; OutputPath = $outputPath; ModulePath = [String[]]$setting.Options.ModulePath; AddModulesToSearchPath = $setting.Options.AddModulesToSearchPath; } # Start the job # $job = Start-Job -ScriptBlock ${function:BuildConfiguration} -InputObject $jobParams; BuildConfiguration -InputObject $jobParams -Verbose:$VerbosePreference; # Build documentation BuildDocumentation -Collection $collection -Path $outputPath -OutputPath $outputPath -Verbose:$VerbosePreference; } catch { Write-Error -Message "Failed to build configuration for $($node.InstanceName). $($_.Exception.Message)"; } } } } # Wait for the job to return # if ($Wait) { # $job | Receive-Job -Wait; # } else { # $job; # } } end { Write-Verbose -Message "[DOKDsc][$dokOperation] END::"; } } function Publish-DOKDscModule { [CmdletBinding()] [OutputType([void])] param ( [Parameter(Mandatory = $False)] [String]$WorkspacePath = $PWD, [Parameter(Mandatory = $False)] [String]$ModuleName, [Parameter(Mandatory = $False)] [String]$ModuleVersion ) begin { $dokOperation = 'Publish'; Write-Verbose -Message "[DOKDsc][$dokOperation] BEGIN::"; } process { # Get workspace settings $setting = Import-DOKDscWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; $modules = GetModule -WorkspacePath $WorkspacePath -Workspace $setting -Verbose:$VerbosePreference; if ($PSBoundParameters.ContainsKey('ModuleName') -or $PSBoundParameters.ContainsKey('ModuleVersion')) { $modules = $modules | Where-Object -FilterScript { ([String]::IsNullOrEmpty($ModuleName) -or $ModuleName -eq $_.ModuleName) -and ([String]::IsNullOrEmpty($ModuleVerion) -or $ModuleVersion -eq $_.ModuleVersion) } if ($Null -eq $modules) { Write-Error -Message ($LocalizedData.ModuleDoesNotExist) -Category ObjectNotFound -ErrorAction Stop; } } # Process each environment foreach ($module in $modules) { $outputPath = GetWorkspacePath -WorkspacePath $WorkspacePath -Path $setting.Options.OutputPath; $publishParams = @{ Module = $module; OutputPath = $outputPath; }; PublishModule @publishParams; } } end { Write-Verbose -Message "[DOKDsc][$dokOperation] END::"; } } function Get-DOKDscModule { [CmdletBinding()] param ( [Parameter(Mandatory = $False)] [String]$WorkspacePath = $PWD, [Parameter(Mandatory = $False)] [String]$ModuleName, [Parameter(Mandatory = $False)] [String]$ModuleVersion ) process { # Get workspace settings $setting = Import-DOKDscWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; # Get matching modules GetModule -WorkspacePath $WorkspacePath -Workspace $setting -Verbose:$VerbosePreference | Where-Object -FilterScript { ([String]::IsNullOrEmpty($ModuleName) -or $_.ModuleName -eq $ModuleName) -and ([String]::IsNullOrEmpty($ModuleVersion) -or $_.ModuleVersion -eq $ModuleVersion) } } } function Start-DOKDscSite { [CmdletBinding()] param ( [Parameter(Mandatory = $False)] [String]$Path = $PWD ) process { if (!(Test-Path -Path $Path -PathType Container)) { Write-Error -Message 'The path is not valid'; return; } # $setting = ReadSetting -Path $Path -Verbose:$VerbosePreference; docfx "$Path\.docfx\docfx.json" --serve } } function Publish-DOKDscSite { [CmdletBinding()] param ( [Parameter(Mandatory = $False)] [String]$WorkspacePath = $PWD ) process { # Get workspace settings # $setting = Import-DOKDscWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; # Generate a docfx.json file # Call docfx build BuildSite -Path "$WorkspacePath\.docfx\docfx.json" -Verbose:$VerbosePreference; } } function Initialize-DOKDsc { [CmdletBinding()] [OutputType([void])] param ( [Parameter(Mandatory = $False)] [Alias('Path')] [String]$WorkspacePath = $PWD, # Use to force the creation of the workspace when the path does not exist [Parameter(Mandatory = $False)] [Switch]$Force = $False ) begin { Write-Verbose -Message "[DOKDsc][Init] BEGIN::"; } process { # Check if the workspace path exists if (!(Test-Path -Path $WorkspacePath)) { if ($Force) { # Force creation of the workspace path New-Item -Path $WorkspacePath -ItemType Directory -Force | Out-Null; } else { Write-Error -Message ($LocalizedData.WorkspacePathDoesNotExist) -Category ObjectNotFound -TargetObject $WorkspacePath -ErrorAction Stop; } } if (!(HasWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference)) { # Create settings WriteWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; } } end { Write-Verbose -Message "[DOKDsc][Init] END::"; } } # Restore workspace modules function Restore-DOKDscModule { [CmdletBinding()] param ( [Parameter(Mandatory = $False)] [String]$WorkspacePath = $PWD, # The name of the module to restore [Parameter(Mandatory = $False)] [String]$ModuleName, # The version of the module to restore [Parameter(Mandatory = $False)] [String]$ModuleVersion ) process { $setting = Import-DOKDscWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; # Get a list of a matching modules $modules = GetModule -WorkspacePath $WorkspacePath -Workspace $setting -Verbose:$VerbosePreference | Where-Object -FilterScript { ([String]::IsNullOrEmpty($ModuleName) -or $_.ModuleName -eq $ModuleName) -and ([String]::IsNullOrEmpty($ModuleVersion) -or $_.ModuleVersion -eq $ModuleVersion) } # Process each matching module foreach ($module in $modules) { if ($module.Type -eq 'Workspace') { } else { RestoreModule -Module $module -OutputPath (GetWorkspacePath -WorkspacePath $WorkspacePath -Path $setting.Options.ModulePath); } } } } function Import-DOKDscWorkspaceSetting { [CmdletBinding()] [OutputType([DevOpsKitDsc.Workspace.WorkspaceSetting])] param ( [Parameter(Mandatory = $False)] [Alias('Path')] [String]$WorkspacePath = $PWD ) process { # Check if the workspace path exists if (!(Test-Path -Path $WorkspacePath)) { Write-Error -Message ($LocalizedData.WorkspacePathDoesNotExist) -Category ObjectNotFound -TargetObject $WorkspacePath -ErrorAction Stop; } return ReadWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; } } function Set-DOKDscWorkspaceOption { [CmdletBinding(SupportsShouldProcess = $True)] [OutputType([void])] param ( [Parameter(Mandatory = $False)] [String]$WorkspacePath = $PWD, [Parameter(Mandatory = $False)] [String]$OutputPath, [Parameter(Mandatory = $False)] [String]$NodePath ) process { # Load current workspace settings $setting = ReadWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; # Track if settings have been changed $settingChanged = $False; # Check for OutputPath parameter if ($PSBoundParameters.ContainsKey('OutputPath')) { # Continue if the parameter is different to the current setting if ($OutputPath -ne $setting.Options.OutputPath) { # Process WhatIf if ($PSCmdlet.ShouldProcess('', '')) { # Update the setting $setting.Options.OutputPath = $OutputPath; # Mark setting as changed $settingChanged = $True; } } } # Check for NodePath parameter if ($PSBoundParameters.ContainsKey('NodePath')) { # Continue if the parameter is different to the current setting if ($OutputPath -ne $setting.Options.NodePath) { # Process WhatIf if ($PSCmdlet.ShouldProcess('', '')) { # Update the setting $setting.Options.NodePath = $NodePath; # Mark setting as changed $settingChanged = $True; } } } # Save workspace settings if any changes were made if ($settingChanged) { # Save workspace settings WriteWorkspaceSetting -InputObject $setting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; } } } function Get-DOKDscWorkspaceOption { [CmdletBinding()] [OutputType([DevOpsKitDsc.Workspace.WorkspaceOption])] param ( [Parameter(Mandatory = $False)] [String]$WorkspacePath = $PWD ) process { # Load current workspace settings $setting = ReadWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; return $setting.Options; } } function Add-DOKDscModule { [CmdletBinding(DefaultParameterSetName = 'Module')] param ( [Parameter(Mandatory = $False)] [String]$WorkspacePath = $PWD, [Parameter(Mandatory = $True, ParameterSetName = 'Module')] [Alias('Name')] [String]$ModuleName, [Parameter(Mandatory = $True, ParameterSetName = 'Module')] [Alias('Version')] [String]$ModuleVersion, [Parameter(Mandatory = $False, ParameterSetName = 'Module')] [String]$Repository, [Parameter(Mandatory = $True, ParameterSetName = 'Path')] [String]$Path, [Parameter(Mandatory = $False)] [ValidateSet('Workspace', 'Repository')] [String]$Type ) begin { # Track if settings have been changed $settingChanged = $False; # Load current workspace settings $setting = ReadWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; } process { $moduleProps = @{ }; if ($PSBoundParameters.ContainsKey('ModuleName')) { $moduleProps['ModuleName'] = $ModuleName; } if ($PSBoundParameters.ContainsKey('ModuleVersion')) { $moduleProps['ModuleVersion'] = $ModuleVersion; } if ($PSBoundParameters.ContainsKey('Repository')) { $moduleProps['Repository'] = $Repository; } if ($PSCmdlet.ParameterSetName -eq 'Path') { # Read the module manifest $manifestPath = GetWorkspacePath -WorkspacePath $WorkspacePath -Path $Path; $manifestName = Split-Path -Path $manifestPath -Leaf; Import-LocalizedData -BindingVariable moduleData -BaseDirectory $manifestPath -FileName "$manifestName.psd1"; $moduleProps['ModuleName'] = $manifestName; $moduleProps['ModuleVersion'] = $moduleData.ModuleVersion; $moduleProps['Path'] = $Path; } if ($PSBoundParameters.ContainsKey('Type')) { $moduleProps['Type'] = $Type; } if (AddModuleToWorkspace -Setting $setting @moduleProps -Verbose:$VerbosePreference) { $settingChanged = $True; } } end { if ($settingChanged) { WriteWorkspaceSetting -WorkspacePath $WorkspacePath -InputObject $setting -Verbose:$VerbosePreference; } } } function Compress-DOKDscWorkspaceModule { [CmdletBinding()] param ( [Parameter(Mandatory = $False)] [Alias('Path')] [String]$WorkspacePath = $PWD ) process { # Check that the workspace path exists if (!(Test-Path -Path $WorkspacePath)) { Write-Error -Message ($LocalizedData.WorkspacePathDoesNotExist -f $WorkspacePath) -Category ObjectNotFound; return; } # Get workspace settings $setting = Import-DOKDscWorkspaceSetting -WorkspacePath $WorkspacePath -Verbose:$VerbosePreference; # Check modules and restore as required foreach ($module in $setting.Modules) { # PackageModule -Module $module -Format AzureAutomationService -ModulePath $setting.Options.ModulePath -Verbose:$VerbosePreference; } } } #endregion Public functions # # Helper functions # #region Helper functions function RegisterNode { [CmdletBinding()] param ( [Parameter(Mandatory = $True)] [PSObject]$Node, [Parameter(Mandatory = $True)] [String]$OutputPath ) process { $sessionParams = @{ ComputerName = $Node.InstanceName }; if ($Node.InstanceName -eq 'localhost' -or $Node.InstanceName -eq $Env:COMPUTERNAME) { $sessionParams['EnableNetworkAccess'] = $True; } $session = New-PSSession @sessionParams; # Setup the encryption certificate TryDscEncryptionCertificate -Session $session -Path $OutputPath -Verbose:$VerbosePreference | Out-Null; } } # Create a public/private keypair as required function TryDscEncryptionCertificate { [CmdletBinding()] [OutputType([Security.Cryptography.X509Certificates.X509Certificate2])] param ( # A remoting session to connect to [Parameter(Mandatory = $True)] [System.Management.Automation.Runspaces.PSSession]$Session, # The path to save the encryption public key to [Parameter(Mandatory = $True)] [String]$Path ) process { # Try to get the encryption certificate $certificate = Invoke-Command -Session $Session -ScriptBlock ${function:GetCertificate}; # Create a new encryption certificate as required if ($Null -eq $certificate) { $certificate = Invoke-Command -Session $Session -ScriptBlock ${function:NewCertificate}; } else { Write-Verbose -Message ($LocalizedData.HasEncryptionCertificate -f $Session.ComputerName, $certificate.Thumbprint); } if ($Null -eq $certificate) { return $Null; } # Strongly type the result $result = New-Object -TypeName Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @(,$certificate.GetRawCertData()); $result | Export-Certificate -FilePath "$Path\$($Session.ComputerName).cer" -Force; # Return the certificate return $result; } } function GetCertificate { [CmdletBinding()] param ( ) process { # Get the DSC encryption certificate with the longest lifetime Get-ChildItem -Path 'Certificate::LocalMachine\My' | Where-Object -FilterScript { $_.FriendlyName -eq 'DSC Credential Encryption' } | Sort-Object -Property NotAfter -Descending | Select-Object -First 1; } } function NewCertificate { [CmdletBinding()] param ( ) process { # Subject processing [String]$Subject = "CN=$Env:COMPUTERNAME"; # http://msdn.microsoft.com/en-us/library/aa377051(VS.85).aspx $SubjectDN = New-Object -ComObject X509Enrollment.CX500DistinguishedName; $SubjectDN.Encode($Subject, 0x0); # SANs New-Variable -Name OtherName -Value 0x1 -Option Constant New-Variable -Name RFC822Name -Value 0x2 -Option Constant New-Variable -Name DNSName -Value 0x3 -Option Constant New-Variable -Name DirectoryName -Value 0x5 -Option Constant New-Variable -Name URL -Value 0x7 -Option Constant New-Variable -Name IPAddress -Value 0x8 -Option Constant New-Variable -Name RegisteredID -Value 0x9 -Option Constant New-Variable -Name Guid -Value 0xa -Option Constant New-Variable -Name UPN -Value 0xb -Option Constant New-Variable -Name AllowUntrustedCertificate -Value 0x2 -Option Constant New-Variable -Name Base64 -Value 0x1 -Option Constant $certificateExtensions = @(); # Enhanced key usage $EnhancedKeyUsage = [Security.Cryptography.Oid]'Document Encryption'; [Security.Cryptography.X509Certificates.X509KeyUsageFlags]$KeyUsage = 'KeyEncipherment, DataEncipherment'; [String[]]$SubjectAlternativeName = $Env:COMPUTERNAME; $ProviderName = 'Microsoft Enhanced Cryptographic Provider v1.0'; $AlgorithmName = 'RSA'; $SignatureAlgorithm = 'SHA256'; $KeyLength = 2048; [String]$FriendlyName = 'DSC Credential Encryption'; $Description = 'This is an encryption certificate for DSC.'; [datetime]$NotBefore = [DateTime]::Now.AddDays(-1); [datetime]$NotAfter = $NotBefore.AddDays(365); $OIDs = New-Object -ComObject X509Enrollment.CObjectIDs; $OID = New-Object -ComObject X509Enrollment.CObjectID; $OID.InitializeFromValue($EnhancedKeyUsage.Value); # http://msdn.microsoft.com/en-us/library/aa376785(VS.85).aspx $OIDs.Add($OID); # http://msdn.microsoft.com/en-us/library/aa378132(VS.85).aspx $EKU = New-Object -ComObject X509Enrollment.CX509ExtensionEnhancedKeyUsage; $EKU.InitializeEncode($OIDs); $certificateExtensions += $EKU; # Build key usage extension $keyUsageExtension = New-Object -ComObject X509Enrollment.CX509ExtensionKeyUsage; $keyUsageExtension.InitializeEncode([int]$KeyUsage); $keyUsageExtension.Critical = $True; $certificateExtensions += $keyUsageExtension; # SAN if ($SubjectAlternativeName) { $SAN = New-Object -ComObject X509Enrollment.CX509ExtensionAlternativeNames $Names = New-Object -ComObject X509Enrollment.CAlternativeNames foreach ($altname in $SubjectAlternativeName) { $Name = New-Object -ComObject X509Enrollment.CAlternativeName if ($altname.Contains("@")) { $Name.InitializeFromString($RFC822Name,$altname) } else { try { $Bytes = [Net.IPAddress]::Parse($altname).GetAddressBytes() $Name.InitializeFromRawData($IPAddress,$Base64,[Convert]::ToBase64String($Bytes)) } catch { try { $Bytes = [Guid]::Parse($altname).ToByteArray() $Name.InitializeFromRawData($Guid,$Base64,[Convert]::ToBase64String($Bytes)) } catch { try { $Bytes = ([Security.Cryptography.X509Certificates.X500DistinguishedName]$altname).RawData $Name.InitializeFromRawData($DirectoryName,$Base64,[Convert]::ToBase64String($Bytes)) } catch {$Name.InitializeFromString($DNSName,$altname)} } } } $Names.Add($Name) } $SAN.InitializeEncode($Names) # $ExtensionsToAdd += "SAN" $certificateExtensions += $SAN; } # Get private key algorithm OID $algorithmOid = New-Object -ComObject X509Enrollment.CObjectId; $algorithmOid.InitializeFromValue(([Security.Cryptography.Oid]$AlgorithmName).Value); # Get signature algorithm OID $signatureOid = New-Object -ComObject X509Enrollment.CObjectId; $signatureOid.InitializeFromValue(([Security.Cryptography.Oid]$SignatureAlgorithm).Value); # Generate a private key # http://msdn.microsoft.com/en-us/library/aa378921(VS.85).aspx $privateKey = New-Object -ComObject X509Enrollment.CX509PrivateKey; $privateKey.ProviderName = $ProviderName; $privateKey.Algorithm = $algorithmOid; # http://msdn.microsoft.com/en-us/library/aa379409(VS.85).aspx $privateKey.KeySpec = 1; # Exchange $privateKey.Length = $KeyLength; $privateKey.MachineContext = $True; $privateKey.ExportPolicy = 0; # Not exportable $privateKey.Create(); # Build the certificate request # http://msdn.microsoft.com/en-us/library/aa377124(VS.85).aspx $csr = New-Object -ComObject X509Enrollment.CX509CertificateRequestCertificate; $csr.InitializeFromPrivateKey(0x2, $privateKey, ''); # Set certificate fields $csr.Subject = $SubjectDN; $csr.Issuer = $csr.Subject; $csr.NotBefore = $NotBefore; $csr.NotAfter = $NotAfter; # Add certificate extensions foreach ($item in $certificateExtensions) { $csr.X509Extensions.Add($item); }; # if (![string]::IsNullOrEmpty($SerialNumber)) { # if ($SerialNumber -match "[^0-9a-fA-F]") {throw "Invalid serial number specified."} # if ($SerialNumber.Length % 2) {$SerialNumber = "0" + $SerialNumber} # $Bytes = $SerialNumber -split "(.{2})" | Where-Object {$_} | ForEach-Object{[Convert]::ToByte($_,16)} # $ByteString = [Convert]::ToBase64String($Bytes) # $Cert.SerialNumber.InvokeSet($ByteString,1) # } $csr.SignatureInformation.HashAlgorithm = $signatureOid; # Completing certificate request template building $csr.Encode(); # interface: http://msdn.microsoft.com/en-us/library/aa377809(VS.85).aspx $certificateEnrollment = New-Object -ComObject X509Enrollment.CX509enrollment; $certificateEnrollment.InitializeFromRequest($csr); $certificateEnrollment.CertificateFriendlyName = $FriendlyName; $certificateEnrollment.CertificateDescription = $Description; # Create the request with base64 encoding $endCert = $certificateEnrollment.CreateRequest(0x1); # Install the certificate response $certificateEnrollment.InstallResponse( 0x2, # Allow untrusted, this self-signed certificate will not chain to a trust root $endCert, 0x1, # Use base64 encoding '' ); [Byte[]]$CertBytes = [Convert]::FromBase64String($endCert); New-Object Security.Cryptography.X509Certificates.X509Certificate2 @(,$CertBytes); } } function CopyModules { [CmdletBinding()] [OutputType([void])] param ( [Parameter(Mandatory = $True)] [System.Management.Automation.Runspaces.PSSession]$Session, # The source path for the modules [Parameter(Mandatory = $True)] [String]$Path ) process { # Copy the module paths into the session Copy-Item -ToSession $Session -Path $Path -Destination 'C:\Program Files\WindowsPowerShell\Modules\' -Recurse -Force; } } function BuildConfiguration { [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $True)] [PSObject]$InputObject ) process { Write-Verbose -Message "[DOKDsc][$dokOperation] -- Building configuration"; $Path = $InputObject.Path; $OutputPath = $InputObject.OutputPath; $ModulePath = $InputObject.ModulePath; $ConfigurationData = $InputObject.ConfigurationData; $Parameters = $InputObject.Parameters; $AddModulesToSearchPath = $InputObject.AddModulesToSearchPath; $currentPSModulePath = $Env:PSModulePath; try { if (![String]::IsNullOrEmpty($ModulePath) -and $AddModulesToSearchPath) { $Env:PSModulePath = "$Env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules;$([String]::Join(';', $ModulePath))"; Write-Verbose -Message "[DOKDsc][$dokOperation] -- Using PSModulePath: $($Env:PSModulePath)"; # [System.Environment]::SetEnvironmentVariable('PSModulePath', "$Env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules;$ModulePath", 'Process') } $configurationScript = "$Path"; Write-Verbose -Message "[DOKDsc][$dokOperation] -- Using configuration script: $configurationScript"; if (!(Test-Path -Path $configurationScript)) { Write-Error -Message "Failed to find configuration script: $configurationScript"; } # By default use the base name of the file as the configuration name $scriptItem = Get-Item -Path $configurationScript; $configurationName = $scriptItem.BaseName; if (![String]::IsNullOrEmpty($InputObject.ConfigurationName)) { $configurationName = $InputObject.ConfigurationName; } Write-Verbose -Message "[DOKDsc][$dokOperation] -- Using configuration name: $ConfigurationName"; Write-Verbose -Message "[DOKDsc][$dokOperation] -- Using OutputPath: $OutputPath"; $configParams = @{ OutputPath = $OutputPath; }; # Bind configuration data if ($Null -ne $ConfigurationData) { $configParams.Add('ConfigurationData', $ConfigurationData); } # Bind parameters if ($Null -ne $Parameters) { $configParams += $Parameters; } . "$configurationScript"; $buildResult = & $ConfigurationName @configParams; if ($Null -ne $buildResult -and $buildResult -is [System.IO.FileInfo]) { Write-Verbose -Message "[DOKDsc][$dokOperation] -- Generating checksum: $($buildResult.FullName)"; New-DscChecksum -Path $buildResult.FullName -Force | Out-Null; } } catch { Write-Error -Message "Failed to build configuration for node. $($_.Exception.Message)" -Exception $_.Exception; } finally { if (![String]::IsNullOrEmpty($ModulePath) -and $AddModulesToSearchPath) { $Env:PSModulePath = $currentPSModulePath; # [System.Environment]::SetEnvironmentVariable('PSModulePath', "$currentPSModulePath", 'Process') } } } } function ImportNodeData { [CmdletBinding()] [OutputType([PSObject[]])] param ( [Parameter(Mandatory = $True)] [String[]]$NodePath, [Parameter(Mandatory = $False)] [AllowEmptyCollection()] [AllowNull()] [String[]]$InstanceName = $Null ) process { Write-Verbose -Message "[DOKDsc][$dokOperation] -- $($LocalizedData.ImportNodeData -f $NodePath)"; foreach ($path in $NodePath) { # Check if the path exists if (!(Test-Path -Path $path)) { # if ($PSBoundParameters.ContainsKey('InstanceName')) { # Write-Error -Message ($LocalizedData.ErrorMissingNodeData -f $NodePath) -Category ObjectNotFound; # } # No node data to process # return $Null; } $pathFilter = Join-Path -Path $path -ChildPath '/'; $result = Get-ChildItem -Path $pathFilter -File | Where-Object -FilterScript { ($Null -eq $InstanceName -or $InstanceName.Count -eq 0) -or $InstanceName -contains $_.BaseName } | ForEach-Object -Process { $item = $_; $dataFilePath = $item.FullName; Write-Verbose -Message "[DOKDsc][$dokOperation] -- $($LocalizedData.FoundNodeData -f $dataFilePath)"; if ($item.Extension -eq '.psd1') { # Process .psd1 file ReadPSNodeData -Path $dataFilePath -Verbose:$VerbosePreference; } elseif ($item.Extension -eq '.json') { # Process .json file ReadJsonNodeData -Path $dataFilePath -Verbose:$VerbosePreference; } } $result; } } } # Read node data from a .psd1 file. function ReadPSNodeData { [CmdletBinding()] param ( [Parameter(Mandatory = $True)] [String]$Path ) process { Write-Verbose -Message "[DOKDsc][$dokOperation] -- Reading node data from $Path"; $baseDirectory = Split-Path -Path $Path -Parent; $fileName = Split-Path -Path $Path -Leaf; $results = @{ }; # Read the .psd1 file Import-LocalizedData -BaseDirectory $baseDirectory -FileName $fileName -BindingVariable psdFile; # Detect if compatible format should be used. if ($psdFile.ContainsKey('AllNodes')) { foreach ($node in $psdFile.AllNodes) { # Create result object $result = New-Object -TypeName PSObject -Property @{ InstanceName = $node.NodeName; BaseDirectory = $baseDirectory; ConfigurationData = @{ AllNodes = @($node); }; } $results.Add($result.InstanceName, $result); } } else { # Create result object $result = New-Object -TypeName PSObject -Property @{ InstanceName = $psdFile.NodeName; BaseDirectory = $baseDirectory; ConfigurationData = $psdFile; } $results.Add($result.InstanceName, $result); } # Emit result objects to pipeline $results.Values; } } # Read node data from a .json file. function ReadJsonNodeData { [CmdletBinding()] param ( [Parameter(Mandatory = $True)] [String]$Path ) process { Write-Verbose -Message "[DOKDsc][$dokOperation] -- Reading node data from $Path"; $baseDirectory = Split-Path -Path $Path -Parent; $results = @{ }; # Convert object properties to a hashtable function ObjectToHashtable { param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [PSObject]$InputObject ) process { $result = @{ }; # Process each property $InputObject.PSObject.Properties.GetEnumerator() | ForEach-Object -Process { if ($_.Value -is [Object[]]) { $result[$_.Name] = $_.Value | ObjectToHashtable; } elseif ($_.Value -is [PSCustomObject]) { $result[$_.Name] = ObjectToHashtable -InputObject $_.Value; } else { $result[$_.Name] = $_.Value; } } $result; } } # Read the .json file in as a hashtable $jsonFile = Get-Content -Path $Path | ConvertFrom-Json | ObjectToHashtable; # Detect if compatible format should be used. if ($jsonFile.ContainsKey('AllNodes')) { foreach ($node in $jsonFile.AllNodes) { # Create result object $result = New-Object -TypeName PSObject -Property @{ InstanceName = $node.NodeName; BaseDirectory = $baseDirectory; ConfigurationData = @{ AllNodes = @($node); }; } $results.Add($result.InstanceName, $result); } } else { # Create result object $result = New-Object -TypeName PSObject -Property @{ InstanceName = $jsonFile.NodeName; BaseDirectory = $baseDirectory; ConfigurationData = $jsonFile; } $results.Add($result.InstanceName, $result); } # Emit result objects to pipeline $results.Values; } } # Merge encryption certificate data function MergeNodeCertificate { [CmdletBinding()] [OutputType([PSObject])] param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [PSObject]$InputObject, [Parameter(Mandatory = $True)] [String]$Path, [Parameter(Mandatory = $True)] [String]$InstanceName, [Parameter(Mandatory = $False)] [Switch]$PassThru = $False ) process { Write-Verbose -Message "[DOKDsc][$dokOperation]`t-- Merging node public key: $InstanceName"; $certificateFile = Join-Path -Path $Path -ChildPath "$InstanceName.cer"; if (Test-Path -Path $certificateFile) { $InputObject.ConfigurationData.AllNodes[0]['CertificateFile'] = $certificateFile; $InputObject.ConfigurationData.AllNodes[0]['Thumbprint'] = (ExtractCertificateThumbprint -Path $certificateFile -Verbose:$VerbosePreference); Write-Verbose -Message "Using certificate: $($InputObject.ConfigurationData.AllNodes[0]['CertificateFile'])"; Write-Verbose -Message "Using thumbprint: $($InputObject.ConfigurationData.AllNodes[0]['Thumbprint'])"; } else { Write-Warning -Message ($LocalizedData.NoEncryptionCertificate -f $InstanceName); } if ($PassThru) { return $InputObject; } } } function MergeConfiguration { [CmdletBinding()] [OutputType([PSObject])] param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [PSObject]$InputObject, [Parameter(Mandatory = $True)] [DevOpsKitDsc.Workspace.Collection]$Collection, [Parameter(Mandatory = $False)] [Switch]$PassThru = $False ) begin { Write-Verbose -Message "[DOKDsc][MergeConfiguration] BEGIN::"; } process { # Check if any configuration data was specified if ($Null -ne $Collection.Data -and $Collection.Data.Count -gt 0) { # Process each key value pair foreach ($kv in $Collection.Data.GetEnumerator()) { # Only replace the configuration data from the node if ReplaceNodeData = true, or the key didn't exist if ($Null -eq $Collection.Options -or $Collection.Options.ReplaceNodeData -or !$InputObject.ConfigurationData.AllNodes[0].ContainsKey($kv.Key)) { Write-Verbose -Message "[DOKDsc][MergeConfiguration] -- Setting $($kv.Key): $($kv.Value)"; $InputObject.ConfigurationData.AllNodes[0][$kv.Key] = $kv.Value; } } } if ($PassThru) { return $InputObject; } } end { Write-Verbose -Message "[DOKDsc][MergeConfiguration] END::"; } } function ExtractCertificateThumbprint { [CmdletBinding()] param ( [Parameter(Mandatory = $True)] [String]$Path ) process { Write-Verbose -Message "Extracting for $Path"; $certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($Path); return $certificate.Thumbprint; } } function CreatePath { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $True)] [String]$Path, [Switch]$File = $False, [Parameter(Mandatory = $False)] [Switch]$PassThru = $False ) process { if ($File) { $Path = Split-Path -Path $Path -Parent; } if (!(Test-Path -Path $Path)) { New-Item -Path $Path -ItemType Directory -Force | Out-Null; } if ($PassThru) { $result = (Get-Item -Path $Path).FullName; return $result; } } } function PublishConfiguration { [CmdletBinding()] [OutputType([void])] param ( [Parameter(Mandatory = $True)] [String]$Name, [Parameter(Mandatory = $True)] [String]$Path, [Parameter(Mandatory = $True)] [String]$OutputPath ) process { if (!(Test-Path -Path $Path)) { Write-Error -Message "The specified configuration does not exist."; return; } Copy-Item -LiteralPath $Path -Destination $OutputPath -Force; } } function PublishModule { [CmdletBinding()] param ( [Parameter(Mandatory = $True)] [DevOpsKitDsc.Workspace.Module]$Module, [Parameter(Mandatory = $True)] [String]$OutputPath ) process { # # Read the .psd1 file # Import-LocalizedData -BaseDirectory $baseDirectory -FileName $fileName -BindingVariable nodeConfigData; $publishName = "$OutputPath\$($Module.ModuleName).zip"; if (!(Test-Path -Path $OutputPath)) { New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null; } Compress-Archive -Path "$($Module.Path)\*" -DestinationPath $publishName -Verbose -Force; } } function GetModule { [CmdletBinding()] param ( [Parameter(Mandatory = $True)] [String]$WorkspacePath, [Parameter(Mandatory = $True)] [DevOpsKitDsc.Workspace.WorkspaceSetting]$Workspace ) process { $modulesPath = GetWorkspacePath -WorkspacePath $WorkspacePath -Path $Workspace.Options.ModulePath; foreach ($m in $Workspace.Modules) { if ([String]::IsNullOrEmpty($m.Path)) { # Calculate and updates the module path $m.Path = "$modulesPath\$($m.ModuleName)\$($m.ModuleVersion)"; } else { $m.Path = GetWorkspacePath -WorkspacePath $WorkspacePath -Path $m.Path; } # Emit the module back to the pipeline $m; } } } function BuildDocumentation { [CmdletBinding()] [OutputType([void])] param ( [Parameter(Mandatory = $True)] [DevOpsKitDsc.Workspace.Collection]$Collection, # The path to the .mof file [Parameter(Mandatory = $True)] [String]$Path, # The output path to store documentaion [Parameter(Mandatory = $True)] [String]$OutputPath ) begin { Write-Verbose -Message "[DOKDsc][$dokOperation][Doc]::BEGIN"; } process { # Only generate documentation if a template has been set if ($Null -eq $Collection.Docs -or [String]::IsNullOrEmpty($Collection.Docs.Path)) { Write-Verbose -Message "[DOKDsc][$dokOperation][Doc] -- Skipping documentation, template not set"; return; } Invoke-DscNodeDocument -DocumentName $Collection.Docs.Name -Script $Collection.Docs.Path -Path $Path -OutputPath $OutputPath -Verbose:$VerbosePreference; # Write-Verbose -Message "[DOKDsc][$dokOperation] -- Update TOC: $($buildResult.FullName)"; # Update TOC UpdateToc -OutputPath $OutputPath -Verbose:$VerbosePreference; } end { Write-Verbose -Message "[DOKDsc][$dokOperation][Doc]::END"; } } function UpdateToc { [CmdletBinding()] [OutputType([void])] param ( [Parameter(Mandatory = $True)] [String]$OutputPath ) begin { Write-Verbose -Message "[DOKDsc][$dokOperation][Toc]::BEGIN"; # Get markdown content files $contentFilePath = @((Get-ChildItem -Path $OutputPath -File | Where-Object -FilterScript { $_.FullName -like '*.md' -and $_.Name -ne 'TOC.md' }).FullName); $toc = @('# Nodes'); # Read each file and extract metadata foreach ($file in $contentFilePath) { $contentHeader = ReadYamlHeader -Path $file -Verbose:$VerbosePreference; $href = Split-Path -Path $file -Leaf; $title = $href -replace '\.md', ''; if ($Null -ne $contentHeader -and $contentHeader.ContainsKey('title')) { $title = $contentHeader.title; } $toc += "## [$title]($href)"; } $toc | Set-Content -Path "$OutputPath\TOC.md"; Write-Verbose -Message "[DOKDsc][$dokOperation][Toc]::END"; } } function ReadYamlHeader { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $True)] [String]$Path ) process { # Read the file $content = Get-Content -Path $Path -Raw; # Detect Yaml header if (![String]::IsNullOrEmpty($content) -and $content -match '^(---\r\n(?<yaml>([A-Z0-9]{1,}:[A-Z0-9 ]{1,}(\r\n){0,}){1,})\r\n---\r\n)') { Write-Verbose -Message "[DscDocs][Toc]`t-- Reading Yaml header: $Path"; # Extract yaml header key value pair [String[]]$yamlHeader = $Matches.yaml -split "`n"; $result = @{ }; # Read key values into hashtable foreach ($item in $yamlHeader) { $kv = $item.Split(':', 2, [System.StringSplitOptions]::RemoveEmptyEntries); Write-Debug -Message "Found yaml keypair from: $item"; if ($kv.Length -eq 2) { $result[$kv[0].Trim()] = $kv[1].Trim(); } } # Emit result to the pipeline return $result; } } } function BuildSite { [CmdletBinding()] [OutputType([void])] param ( [Parameter(Mandatory = $True)] [String]$Path ) process { docfx.exe build $Path; } } function HasWorkspaceSetting { [CmdletBinding()] [OutputType([DevOpsKitDsc.Workspace.WorkspaceSetting])] param ( [Parameter(Mandatory = $True)] [String]$WorkspacePath ) process { $settingsPath = Join-Path -Path $WorkspacePath -ChildPath '\.dokd\settings.json'; return (Test-Path -Path $settingsPath); } } function ReadWorkspaceSetting { [CmdletBinding()] [OutputType([DevOpsKitDsc.Workspace.WorkspaceSetting])] param ( [Parameter(Mandatory = $True)] [String]$WorkspacePath ) process { $settingsPath = Join-Path -Path $WorkspacePath -ChildPath '\.dokd\settings.json'; if (!(Test-Path -Path $settingsPath)) { return [DevOpsKitDsc.Workspace.WorkspaceHelper]::LoadDefault(); } $settingsPath = Resolve-Path -Path $settingsPath; Write-Verbose -Message "[DOKDsc][$dokOperation] -- Loading workspace from: $WorkspacePath"; $result = [DevOpsKitDsc.Workspace.WorkspaceHelper]::LoadFrom($settingsPath); if ($Null -eq $result) { return [DevOpsKitDsc.Workspace.WorkspaceHelper]::LoadDefault(); } return $result; } } function WriteWorkspaceSetting { [CmdletBinding()] param ( [Parameter(Mandatory = $True)] [String]$WorkspacePath, [Parameter(Mandatory = $False)] [DevOpsKitDsc.Workspace.WorkspaceSetting]$InputObject = [DevOpsKitDsc.Workspace.WorkspaceHelper]::LoadDefault() ) process { $dokdPath = Join-Path -Path $WorkspacePath -ChildPath '\.dokd'; $settingsPath = Join-Path -Path $dokdPath -ChildPath '\settings.json'; if (!(Test-Path -Path $dokdPath)) { New-Item -Path $dokdPath -ItemType Directory -Force | Out-Null; } [DevOpsKitDsc.Workspace.WorkspaceHelper]::SaveTo($settingsPath, $InputObject); } } function AddModuleToWorkspace { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $True)] [DevOpsKitDsc.Workspace.WorkspaceSetting]$Setting, [Parameter(Mandatory = $True)] [String]$ModuleName, [Parameter(Mandatory = $True)] [String]$ModuleVersion, [Parameter(Mandatory = $False)] [String]$Repository, [Parameter(Mandatory = $False)] [String]$Path, [Parameter(Mandatory = $False)] [String]$Type ) process { $moduleObject = New-Object -TypeName DevOpsKitDsc.Workspace.Module -Property @{ ModuleName = $ModuleName ModuleVersion = $ModuleVersion }; if ($PSBoundParameters.ContainsKey('Repository')) { $moduleObject.Repository = $Repository; } if ($PSBoundParameters.ContainsKey('Path')) { $moduleObject.Path = $Path; } if ($PSBoundParameters.ContainsKey('Type')) { $moduleObject.Type = $TYpe; } $Setting.Modules.Add($moduleObject); return $True; } } function RestoreModule { [CmdletBinding()] [OutputType([void])] param ( [Parameter(Mandatory = $True)] [DevOpsKitDsc.Workspace.Module]$Module, [Parameter(Mandatory = $True)] [String]$OutputPath ) process { Write-Verbose -Message "[DOKDsc][$dokOperation] -- Restoring module: $Module"; # Check if the module has already been saved to the modules path $moduleLocation = Join-Path -Path $OutputPath -ChildPath "$($Module.ModuleName)\$($Module.ModuleVersion)"; if (!(Test-Path -Path "$moduleLocation\$($Module.ModuleName).ps*" -Include '*.psm1', '*.psd1')) { try { SaveModule -Module $Module -OutputPath $OutputPath -ErrorAction Stop; } catch { Write-Error -Message ($LocalizedData.ModuleRestoreError -f $Module.ModuleName, $_.Exception.Message) -Exception $_.Exception -Category ObjectNotFound; } } else { Write-Verbose -Message "[DOKDsc][$dokOperation] -- Module already exists: $Module"; } } } function SaveModule { [CmdletBinding()] param ( [Parameter(Mandatory = $True)] [DevOpsKitDsc.Workspace.Module]$Module, [Parameter(Mandatory = $True)] [String]$OutputPath ) process { $moduleFilter = @{ Name = $Module.ModuleName; RequiredVersion = $Module.ModuleVersion; Path = $OutputPath; }; if (![String]::IsNullOrEmpty($Module.Repository)) { $moduleFilter.Add('Repository', $Module.Repository); } Save-Module @moduleFilter; } } # Compress a specific module for either a Pull Server or Azure Automation Service function PackageModule { [CmdletBinding()] param ( [Parameter(Mandatory = $True)] [DevOpsKitDsc.Workspace.Module]$Module, [Parameter(Mandatory = $True)] [ValidateSet('AzureAutomationService')] [String]$Format, [Parameter(Mandatory = $True)] [String]$ModulePath ) begin { Write-Verbose -Message "[DOKDsc][PackageModule]`tBEGIN::"; } process { Write-Verbose -Message "[DOKDsc][PackageModule] -- Packaging module: $Module"; if ($Format -eq 'AzureAutomationService') { } } end { Write-Verbose -Message "[DOKDsc][PackageModule]`tEND::"; } } function GetWorkspacePath { [CmdletBinding()] [OutputType([String])] param ( [Parameter(Mandatory = $True)] [String]$WorkspacePath, [Parameter(Mandatory = $False)] [String]$Path, [Parameter(Mandatory = $False)] [String]$ChildPath, [Parameter(Mandatory = $False)] [Switch]$Relative = $False ) process { $result = $Path; $WorkspacePath = Resolve-Path -Path $WorkspacePath; if (![System.IO.Path]::IsPathRooted($Path)) { $result = Join-Path -Path $WorkspacePath -ChildPath $Path; } if ($PSBoundParameters.ContainsKey('ChildPath') -and ![String]::IsNullOrEmpty($ChildPath)) { $result = Join-Path -Path $result -ChildPath $ChildPath; } # Make the path relative to workspace path if ($Relative) { if ($result.Contains($WorkspacePath)) { $result = $result.Replace($WorkspacePath, '.'); } } return $result; } } function CopyTemplate { [CmdletBinding()] param ( [Parameter(Mandatory = $True)] [String]$Name, [Parameter(Mandatory = $True)] [String]$Path ) process { $templateFilePath = Join-Path -Path $PSScriptRoot -ChildPath "Templates\$Name"; CreatePath -Path $configurationPath -File; Set-Content -Path $configurationPath -Value (Get-Content -Path $templateFilePath); } } function GetDefaultConfigurationPath { [CmdletBinding()] [OutputType([String])] param ( [Parameter(Mandatory = $True)] [String]$WorkspacePath, [Parameter(Mandatory = $True)] [DevOpsKitDsc.Workspace.WorkspaceSetting]$Setting, [Parameter(Mandatory = $True)] [String]$ConfigurationName ) process { return GetWorkspacePath -WorkspacePath $WorkspacePath -Path "src\Configuration\$ConfigurationName.ps1"; } } #endregion Helper functions # # Export module # New-Alias -Name 'dokd-init' -Value 'Initialize-DOKDsc'; New-Alias -Name 'dokd-restore' -Value 'Restore-DOKDscModule'; New-Alias -Name 'dokd-new' -Value 'New-DOKDscCollection'; New-Alias -Name 'dokd-build' -Value 'Invoke-DOKDscBuild'; Export-ModuleMember -Alias @( 'dokd-init' 'dokd-restore' 'dokd-new' 'dokd-build' ); Export-ModuleMember -Function @( 'Initialize-DOKDsc' 'Import-DOKDscNodeConfiguration' 'Register-DOKDscNode' 'Invoke-DOKDscBuild' 'Publish-DOKDscCollection' 'New-DOKDscCollection' 'Get-DOKDscCollection' 'Import-DOKDscWorkspaceSetting' 'Set-DOKDscWorkspaceOption' 'Get-DOKDscWorkspaceOption' 'Add-DOKDscModule' 'Get-DOKDscModule' 'Publish-DOKDscModule' 'Restore-DOKDscModule' ); # EOM |