diff --git a/.gitattributes b/.gitattributes index d48b9743..4460ddaf 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,17 @@ -# Ensure these files are treated as binary -*.exe binary \ No newline at end of file +# Needed for publishing of examples, build worker defaults to core.autocrlf=input. +* text eol=autocrlf + +*.mof text eol=crlf +*.sh text eol=lf +*.svg eol=lf + +# Ensure any exe files are treated as binary +*.exe binary +*.jpg binary +*.xl* binary +*.pfx binary +*.png binary +*.dll binary +*.so binary +*.zip binary +*.vsix binary diff --git a/.github/ISSUE_TEMPLATE/General.md b/.github/ISSUE_TEMPLATE/General.md new file mode 100644 index 00000000..fbcdf240 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/General.md @@ -0,0 +1,7 @@ +--- +name: General question or documentation update +about: If you have a general question or documentation update suggestion around the resource module. +--- + diff --git a/.github/ISSUE_TEMPLATE/Problem_with_module.yml b/.github/ISSUE_TEMPLATE/Problem_with_module.yml new file mode 100644 index 00000000..bbd3c8eb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Problem_with_module.yml @@ -0,0 +1,102 @@ +name: Problem with the module +description: If you have a problem using this module, want to report a bug, or suggest an enhancement to this module. +labels: [] +assignees: [] +body: + - type: markdown + attributes: + value: | + TITLE: Please be descriptive not sensationalist. + + Your feedback and support is greatly appreciated, thanks for contributing! + + Please provide information regarding your issue under each section below. + **Write N/A in sections that do not apply, or if the information is not available.** + - type: textarea + id: description + attributes: + label: Problem description + description: Details of the scenario you tried and the problem that is occurring, or the enhancement you are suggesting. + validations: + required: true + - type: textarea + id: logs + attributes: + label: Verbose logs + description: | + Verbose logs showing the problem. **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as plain text._ + placeholder: | + Paste verbose logs here + render: text + validations: + required: true + - type: textarea + id: reproducible + attributes: + label: How to reproduce + description: Provide the steps to reproduce the problem. + validations: + required: true + - type: textarea + id: expectedBehavior + attributes: + label: Expected behavior + description: Describe what you expected to happen. + validations: + required: true + - type: textarea + id: currentBehavior + attributes: + label: Current behavior + description: Describe what actually happens. + validations: + required: true + - type: textarea + id: suggestedSolution + attributes: + label: Suggested solution + description: Do you have any suggestions how to solve the issue? + validations: + required: true + - type: textarea + id: targetNodeOS + attributes: + label: Operating system the target node is running + description: | + Please provide as much as possible about the node running CommonTasks2. _Will be automatically formatted as plain text._ + + To help with this information: + - On a Linux distribution, please provide the distribution name, version, and release. The following command can help get this information: `cat /etc/*-release && cat /proc/version` + - On a Windows OS please provide edition, version, build, and language. The following command can help get this information: `Get-ComputerInfo -Property @('OsName','OsOperatingSystemSKU','OSArchitecture','WindowsVersion','WindowsBuildLabEx','OsLanguage','OsMuiLanguages')` + placeholder: | + Add operating system information here + render: text + validations: + required: true + - type: textarea + id: targetNodePS + attributes: + label: PowerShell version and build the target node is running + description: | + Please provide the version and build of PowerShell the target node is running. _Will be automatically formatted as plain text._ + + To help with this information, please run this command: `$PSVersionTable` + placeholder: | + Add PowerShell information here + render: text + validations: + required: true + - type: textarea + id: moduleVersion + attributes: + label: Module version used + description: | + Please provide the version of the CommonTasks2 module that was used. _Will be automatically formatted as plain text._ + + To help with this information, please run this command: `Get-Module -Name 'CommonTasks2' -ListAvailable | ft Name,Version,Path` + placeholder: | + Add module information here + render: text + validations: + required: true + diff --git a/.github/ISSUE_TEMPLATE/Problem_with_resource.yml b/.github/ISSUE_TEMPLATE/Problem_with_resource.yml new file mode 100644 index 00000000..ca90191c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Problem_with_resource.yml @@ -0,0 +1,87 @@ +name: Problem with a resource +description: If you have a problem, bug, or enhancement with a resource in this resource module. +labels: [] +assignees: [] +body: + - type: markdown + attributes: + value: | + Please prefix the issue title (above) with the resource name, e.g. 'ResourceName: Short description of my issue'! + + Your feedback and support is greatly appreciated, thanks for contributing! + - type: textarea + id: description + attributes: + label: Problem description + description: Details of the scenario you tried and the problem that is occurring. + validations: + required: true + - type: textarea + id: logs + attributes: + label: Verbose logs + description: | + Verbose logs showing the problem. **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as plain text._ + placeholder: | + Paste verbose logs here + render: text + validations: + required: true + - type: textarea + id: configuration + attributes: + label: DSC configuration + description: | + The DSC configuration that is used to reproduce the issue (as detailed as possible). **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as PowerShell code._ + placeholder: | + Paste DSC configuration here + render: powershell + validations: + required: true + - type: textarea + id: suggestedSolution + attributes: + label: Suggested solution + description: Do you have any suggestions how to solve the issue? + validations: + required: true + - type: textarea + id: targetNodeOS + attributes: + label: Operating system the target node is running + description: | + Please provide as much as possible about the target node, for example edition, version, build, and language. _Will be automatically formatted as plain text._ + + On OS with WMF 5.1 the following command can help get this information: `Get-ComputerInfo -Property @('OsName','OsOperatingSystemSKU','OSArchitecture','WindowsVersion','WindowsBuildLabEx','OsLanguage','OsMuiLanguages')` + placeholder: | + Add operating system information here + render: text + validations: + required: true + - type: textarea + id: targetNodePS + attributes: + label: PowerShell version and build the target node is running + description: | + Please provide the version and build of PowerShell the target node is running. _Will be automatically formatted as plain text._ + + To help with this information, please run this command: `$PSVersionTable` + placeholder: | + Add PowerShell information here + render: text + validations: + required: true + - type: textarea + id: moduleVersion + attributes: + label: CommonTasks2 version + description: | + Please provide the version of the CommonTasks2 module that was used. _Will be automatically formatted as plain text._ + + To help with this information, please run this command: `Get-Module -Name 'CommonTasks2' -ListAvailable | ft Name,Version,Path` + placeholder: | + Add module information here + render: text + validations: + required: true + diff --git a/.github/ISSUE_TEMPLATE/Resource_proposal.yml b/.github/ISSUE_TEMPLATE/Resource_proposal.yml new file mode 100644 index 00000000..2ddd0986 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Resource_proposal.yml @@ -0,0 +1,39 @@ +name: New resource proposal +description: If you have a new resource proposal that you think should be added to this resource module. +title: "NewResourceName: New resource proposal" +labels: [] +assignees: [] +body: + - type: markdown + attributes: + value: | + Please replace `NewResourceName` in the issue title (above) with your proposed resource name. + + Thank you for contributing and making this resource module better! + - type: textarea + id: description + attributes: + label: Resource proposal + description: Provide information how this resource will/should work and how it will help users. + validations: + required: true + - type: textarea + id: proposedProperties + attributes: + label: Proposed properties + description: | + List all the proposed properties that the resource should have (key, required, write, and/or read). For each property provide a detailed description, the data type, if a default value should be used, and if the property is limited to a set of values. + value: | + Property | Type qualifier | Data type | Description | Default value | Allowed values + --- | --- | --- | --- | --- | --- + PropertyName | Key | String | Detailed description | None | None + validations: + required: true + - type: textarea + id: considerations + attributes: + label: Special considerations or limitations + description: | + Provide any considerations or limitations you can think of that a contributor should take in account when coding the proposed resource, and or what limitations a user will encounter or should consider when using the proposed resource. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..9917040e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +blank_issues_enabled: false +contact_links: + - name: "Virtual PowerShell User Group #DSC channel" + url: https://dsccommunity.org/community/contact/ + about: "To talk to the community and maintainers of DSC Community, please visit the #DSC channel." + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..4b839df3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,63 @@ +# Pull Request + + + +## Pull Request (PR) description + + + +## Task list + + + +- [ ] The PR represents a single logical change. i.e. Cosmetic updates should go in different PRs. +- [ ] Added an entry under the Unreleased section of in the CHANGELOG.md as per [format](https://keepachangelog.com/en/1.0.0/). +- [ ] Local clean build passes without issue or fail tests (`build.ps1 -ResolveDependency`). +- [ ] Resource documentation added/updated in README.md. +- [ ] Resource parameter descriptions added/updated in README.md, schema.mof + and comment-based help. +- [ ] Comment-based help added/updated. +- [ ] Localization strings added/updated in all localization files as appropriate. +- [ ] Examples appropriately added/updated. +- [ ] Unit tests added/updated. See [DSC Resource Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). +- [ ] Integration tests added/updated (where possible). See [DSC Resource Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). +- [ ] New/changed code adheres to [DSC Resource Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md) and [Best Practices](https://github.com/PowerShell/DscResources/blob/master/BestPractices.md). diff --git a/.gitignore b/.gitignore index 8c6b204b..ab2af191 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,16 @@ -DSC/BuildOutput/* +output/ -DSC/DscConfigurations/* -DSC/DscResources/* -Lab/testResults.xml \ No newline at end of file +**.bak +*.local.* +!**/README.md +.kitchen/ + +*.suo +*.user +*.coverage +.vs +.psproj +.sln +markdownissues.txt +node_modules +package-lock.json diff --git a/.vscode/analyzersettings.psd1 b/.vscode/analyzersettings.psd1 new file mode 100644 index 00000000..78312d2c --- /dev/null +++ b/.vscode/analyzersettings.psd1 @@ -0,0 +1,44 @@ +@{ + CustomRulePath = '.\output\RequiredModules\DscResource.AnalyzerRules' + includeDefaultRules = $true + IncludeRules = @( + # DSC Resource Kit style guideline rules. + 'PSAvoidDefaultValueForMandatoryParameter', + 'PSAvoidDefaultValueSwitchParameter', + 'PSAvoidInvokingEmptyMembers', + 'PSAvoidNullOrEmptyHelpMessageAttribute', + 'PSAvoidUsingCmdletAliases', + 'PSAvoidUsingComputerNameHardcoded', + 'PSAvoidUsingDeprecatedManifestFields', + 'PSAvoidUsingEmptyCatchBlock', + 'PSAvoidUsingInvokeExpression', + 'PSAvoidUsingPositionalParameters', + 'PSAvoidShouldContinueWithoutForce', + 'PSAvoidUsingWMICmdlet', + 'PSAvoidUsingWriteHost', + 'PSDSCReturnCorrectTypesForDSCFunctions', + 'PSDSCStandardDSCFunctionsInResource', + 'PSDSCUseIdenticalMandatoryParametersForDSC', + 'PSDSCUseIdenticalParametersForDSC', + 'PSMisleadingBacktick', + 'PSMissingModuleManifestField', + 'PSPossibleIncorrectComparisonWithNull', + 'PSProvideCommentHelp', + 'PSReservedCmdletChar', + 'PSReservedParams', + 'PSUseApprovedVerbs', + 'PSUseCmdletCorrectly', + 'PSUseOutputTypeCorrectly', + 'PSAvoidGlobalVars', + 'PSAvoidUsingConvertToSecureStringWithPlainText', + 'PSAvoidUsingPlainTextForPassword', + 'PSAvoidUsingUsernameAndPasswordParams', + 'PSDSCUseVerboseMessageInDSCResource', + 'PSShouldProcess', + 'PSUseDeclaredVarsMoreThanAssignments', + 'PSUsePSCredentialType', + + 'Measure-*' + ) + +} diff --git a/.vscode/launch.json b/.vscode/launch.json index 6f2dbfe1..8f274b6e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,18 +7,18 @@ { "type": "PowerShell", "request": "launch", - "name": "Launch DSC Build", - "script": "${workspaceRoot}/DSC/Build.ps1", + "name": "Launch Build", + "script": "${workspaceRoot}/Build.ps1", "args": [], - "cwd": "${workspaceRoot}/DSC/" + "cwd": "${workspaceRoot}" }, { "type": "PowerShell", "request": "launch", - "name": "Launch DSC Build -ResolveDependency", - "script": "${workspaceRoot}/DSC/Build.ps1", + "name": "Launch Build -ResolveDependency", + "script": "${workspaceRoot}/Build.ps1", "args": ["-ResolveDependency"], - "cwd": "${workspaceRoot}/DSC/" + "cwd": "${workspaceRoot}" }, { "type": "PowerShell", diff --git a/.vscode/settings.json b/.vscode/settings.json index 8f78b17b..8bf1c69c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,41 @@ { - "powershell.codeFormatting.preset": "OTBS", - "powershell.powerShellDefaultVersion": "Windows PowerShell (x64)" + "powershell.codeFormatting.openBraceOnSameLine": false, + "powershell.codeFormatting.newLineAfterOpenBrace": true, + "powershell.codeFormatting.newLineAfterCloseBrace": true, + "powershell.codeFormatting.whitespaceBeforeOpenBrace": true, + "powershell.codeFormatting.whitespaceBeforeOpenParen": true, + "powershell.codeFormatting.whitespaceAroundOperator": true, + "powershell.codeFormatting.whitespaceAfterSeparator": true, + "powershell.codeFormatting.ignoreOneLineBlock": false, + "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationAfterEveryPipeline", + "powershell.codeFormatting.preset": "Custom", + "powershell.codeFormatting.alignPropertyValuePairs": true, + "powershell.developer.bundledModulesPath": "${cwd}/output/RequiredModules", + "powershell.scriptAnalysis.settingsPath": ".vscode\\analyzersettings.psd1", + "powershell.scriptAnalysis.enable": true, + "files.trimTrailingWhitespace": true, + "files.trimFinalNewlines": true, + "files.insertFinalNewline": true, + "files.associations": { + "*.ps1xml": "xml" + }, + "cSpell.words": [ + "COMPANYNAME", + "ICONURI", + "LICENSEURI", + "PROJECTURI", + "RELEASENOTES", + "buildhelpers", + "endregion", + "gitversion", + "icontains", + "keepachangelog", + "notin", + "pscmdlet", + "steppable" + ], + "[markdown]": { + "files.trimTrailingWhitespace": false, + "files.encoding": "utf8" + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..29911402 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,125 @@ +{ + "version": "2.0.0", + "_runner": "terminal", + "windows": { + "options": { + "shell": { + "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command" + ] + } + } + }, + "linux": { + "options": { + "shell": { + "executable": "/usr/bin/pwsh", + "args": [ + "-NoProfile", + "-Command" + ] + } + } + }, + "osx": { + "options": { + "shell": { + "executable": "/usr/local/bin/pwsh", + "args": [ + "-NoProfile", + "-Command" + ] + } + } + }, + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "&${cwd}/build.ps1", + "args": [], + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "new", + "clear": false + }, + "runOptions": { + "runOn": "default" + }, + "problemMatcher": [ + { + "owner": "powershell", + "fileLocation": [ + "absolute" + ], + "severity": "error", + "pattern": [ + { + "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", + "message": 1 + }, + { + "regexp": "(.*)", + "code": 1 + }, + { + "regexp": "" + }, + { + "regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*", + "file": 1, + "line": 2 + } + ] + } + ] + }, + { + "label": "test", + "type": "shell", + "command": "&${cwd}/build.ps1", + "args": ["-AutoRestore","-Tasks","test"], + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "dedicated", + "showReuseMessage": true, + "clear": false + }, + "problemMatcher": [ + { + "owner": "powershell", + "fileLocation": [ + "absolute" + ], + "severity": "error", + "pattern": [ + { + "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", + "message": 1 + }, + { + "regexp": "(.*)", + "code": 1 + }, + { + "regexp": "" + }, + { + "regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*", + "file": 1, + "line": 2 + } + ] + } + ] + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..2755a7c6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog for DscPipeline + +The format is based on and uses the types of changes according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- For new features. + +### Changed + +- For changes in existing functionality. + +### Deprecated + +- For soon-to-be removed features. + +### Removed + +- For now removed features. + +### Fixed + +- For any bug fix. + +### Security + +- In case of vulnerabilities. + diff --git a/DSC/.work/Cleanup.ps1 b/DSC/.work/Cleanup.ps1 deleted file mode 100644 index f09708eb..00000000 --- a/DSC/.work/Cleanup.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -#$computers = Get-ADComputer -Filter * -$nodes = 'DSCFile01.contoso.com', 'DSCFile02.contoso.com', 'DSCFile03.contoso.com', 'DSCWeb01.contoso.com', 'DSCWeb03.contoso.com', 'DSCWeb02.contoso.com' -$buildWorkers = 'DSCPull01.contoso.com', 'DSCTFS01.contoso.com' - -Invoke-Command -ScriptBlock { - Remove-Item HKLM:\SOFTWARE\DscLcmController\ -Recurse -Force - Remove-DscConfigurationDocument -Stage Current, Pending, Previous - Remove-Item -Path C:\ProgramData\Dsc -Force -Recurse - Get-ScheduledTask | Where-Object TaskName -like *dsc* | Unregister-ScheduledTask -Confirm:$false -} -ComputerName $nodes - -Invoke-Command -ScriptBlock { - dir C:\BuildWorkerSetupFiles\_work | Where-Object { $_.Name.Length -lt 3 } | Remove-Item -Recurse -Force -} -ComputerName $buildWorkers diff --git a/DSC/.work/Node Generation.ps1 b/DSC/.work/Node Generation.ps1 deleted file mode 100644 index f519de84..00000000 --- a/DSC/.work/Node Generation.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -$d = New-DatumStructure -DefinitionFile $env:BHProjectPath\DscConfigData\Datum.yml -$roles = $d.Roles | Get-Member -MemberType ScriptProperty | Select-Object -ExpandProperty Name | Where-Object { $_ -notin 'LCM' } - -$yaml = @" -NodeName: {0} -Environment: Dev -Role: {2} -Description: File Server in Dev -Location: Frankfurt - -NetworkIpConfiguration: - IpAddress: 192.168.111.{1} - Prefix: 24 - Gateway: 192.168.111.50 - DnsServer: 192.168.111.10 - InterfaceAlias: Ethernet - DisableNetbios: True - -PSDscAllowPlainTextPassword: True -PSDscAllowDomainUser: True - -LcmConfig: - ConfigurationRepositoryWeb: - Server: - ConfigurationNames: {0} -"@ - -1..100 | ForEach-Object { - $nodeName = 'DSCFile{0:D4}' -f $_ - $newYaml = $yaml -f $nodeName, $_, ($roles | Get-Random) - $newYaml | Out-File -FilePath "$PSScriptRoot\..\DscConfigData\AllNodes\Dev\$nodeName.yml" -} \ No newline at end of file diff --git a/DSC/.work/Remove-CertificateFromNodes.ps1 b/DSC/.work/Remove-CertificateFromNodes.ps1 deleted file mode 100644 index e18a23a1..00000000 --- a/DSC/.work/Remove-CertificateFromNodes.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -Push-Location -Set-Location -Path $env:BHProjectPath\DscConfigData\AllNodes -$files = dir -Recurse -Filter *.yml - -foreach ($file in $files) { - $y = $file | Get-Content -Raw | ConvertFrom-Yaml -Ordered - if ($y.CertificateFile) { - $y.Remove('CertificateFile') - if (-not $y.ContainsKey('PSDscAllowPlainTextPassword')) { - $y.Add('PSDscAllowPlainTextPassword', $true) - } - $y | ConvertTo-Yaml -OutFile $file.FullName -Options EmitDefaults -Force - } -} - -Pop-Location \ No newline at end of file diff --git a/DSC/Build.ps1 b/DSC/Build.ps1 deleted file mode 100644 index d2c1aa60..00000000 --- a/DSC/Build.ps1 +++ /dev/null @@ -1,188 +0,0 @@ -[CmdletBinding()] -param ( - [string] - $BuildOutput = 'BuildOutput', - - [string] - $ResourcesFolder = 'DscResources', - - [string] - $ConfigDataFolder = 'DscConfigData', - - [string] - $ConfigurationsFolder = 'DscConfigurations', - - [string] - $TestFolder = 'Tests', - - [ScriptBlock] - $Filter = {}, - - [int] - $CurrentJobNumber = 1, - - [int] - $TotalJobCount = 1, - - [string] - $Repository = 'PSGallery', - - [uri] - $GalleryProxy, - - [Parameter(Position = 0)] - $Tasks, - - [switch] - $ResolveDependency, - - [string] - $ProjectPath, - - [switch] - $DownloadResourcesAndConfigurations, - - [switch] - $Help, - - [ScriptBlock] - $TaskHeader = { - Param($Path) - '' - '=' * 79 - Write-Build Cyan "`t`t`t$($Task.Name.Replace('_',' ').ToUpper())" - Write-Build DarkGray "$(Get-BuildSynopsis $Task)" - '-' * 79 - Write-Build DarkGray " $Path" - Write-Build DarkGray " $($Task.InvocationInfo.ScriptName):$($Task.InvocationInfo.ScriptLineNumber)" - '' - } -) - -[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 - -$env:BHBuildStartTime = Get-Date -Write-Host "Current Process ID is '$PID'" - -#changing the path is required to make PSDepend run without internet connection. It is required to download nutget.exe once first: -#Invoke-WebRequest -Uri 'https://aka.ms/psget-nugetexe' -OutFile C:\ProgramData\Microsoft\Windows\PowerShell\PowerShellGet\nuget.exe -ErrorAction Stop -$pathElements = $env:Path -split ';' -$pathElements += 'C:\ProgramData\Microsoft\Windows\PowerShell\PowerShellGet' -$env:Path = $pathElements -join ';' - -#cannot be a default parameter value due to https://github.com/PowerShell/PowerShell/issues/4688 -if (-not $ProjectPath) { - $ProjectPath = $PSScriptRoot -} - -if (-not ([System.IO.Path]::IsPathRooted($BuildOutput))) { - $BuildOutput = Join-Path -Path $ProjectPath -ChildPath $BuildOutput -} - -$buildModulesPath = Join-Path -Path $BuildOutput -ChildPath 'Modules' -if (-not (Test-Path -Path $buildModulesPath)) { - $null = mkdir -Path $buildModulesPath -Force -} - -$configurationPath = Join-Path -Path $ProjectPath -ChildPath $ConfigurationsFolder -$resourcePath = Join-Path -Path $ProjectPath -ChildPath $ResourcesFolder -$configDataPath = Join-Path -Path $ProjectPath -ChildPath $ConfigDataFolder -$testsPath = Join-Path -Path $ProjectPath -ChildPath $TestFolder - -$psModulePathElemets = $env:PSModulePath -split ';' -if ($buildModulesPath -notin $psModulePathElemets) { - $env:PSModulePath = $psModulePathElemets -join ';' - $env:PSModulePath += ";$buildModulesPath" -} - -#importing all resources from 'Build' directory -Get-ChildItem -Path "$PSScriptRoot/Build" -Recurse -Include *.psm1 | - ForEach-Object { - - try { - Import-Module -Name $_.FullName -Scope Global -Force -ErrorAction Stop - Write-Verbose "Imported file $($_.BaseName)" - } - catch { - Write-Warning "Could not import file $($_.BaseName)" - } - } - -Get-ChildItem -Path "$PSScriptRoot/Build" -Recurse -Include *.ps1 | - ForEach-Object { - - try { - . $_.FullName - Write-Verbose "Imported file $($_.BaseName)" - } - catch { } - } - -if (-not (Get-Module -Name InvokeBuild -ListAvailable) -and -not $ResolveDependency) { - Write-Error "Requirements are missing. Please call the script again with the switch 'ResolveDependency'" - return -} - -if ($ResolveDependency) { - . $PSScriptRoot/Build/BuildHelpers/Resolve-Dependency.ps1 - Resolve-Dependency -} - -if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') { - if ($ResolveDependency -or $PSBoundParameters['ResolveDependency']) { - $PSBoundParameters.Remove('ResolveDependency') - $PSBoundParameters['DownloadResourcesAndConfigurations'] = $true - } - - if ($Help) { - Invoke-Build ? - } - else { - $PSBoundParameters.Remove('Tasks') | Out-Null - Invoke-Build -Tasks $Tasks -File $MyInvocation.MyCommand.Path @PSBoundParameters - } - - if (($Tasks -contains 'CompileRootConfiguration' -or $Tasks -contains 'CompileRootMetaMof') -or -not $Tasks) { - Invoke-Build -File "$ProjectPath\PostBuild.ps1" - } - - $mofFileCount = (Get-ChildItem -Path "$BuildOutput\MOF" -Filter *.mof -ErrorAction SilentlyContinue).Count - Write-Host "Created $mofFileCount MOF files in '$BuildOutput/MOF'" -ForegroundColor Green - - #Debug Output - #Write-Host "------------------------------------" -ForegroundColor Magenta - #Write-Host "PowerShell Variables" -ForegroundColor Magenta - #Get-Variable | Out-String | Write-Host -ForegroundColor Magenta - #Write-Host "------------------------------------" -ForegroundColor Magenta - #Write-Host "Environment Variables" -ForegroundColor Magenta - #dir env: | Out-String | Write-Host -ForegroundColor Magenta - #Write-Host "------------------------------------" -ForegroundColor Magenta - - return -} - -if ($TaskHeader) { - Set-BuildHeader $TaskHeader -} - -if (-not $Tasks) { - task . Init, - CleanBuildOutput, - SetPsModulePath, - LoadDatumConfigData, - TestConfigData, - VersionControl, - CompileDatumRsop, - TestDscResources, - CompileRootConfiguration, - CompileRootMetaMof -} -else { - task . $Tasks -} - -Write-Host 'Running the folling tasks:' -ForegroundColor Magenta -${*}.All[-1].Jobs | ForEach-Object { "`t$_" } | Write-Host -Write-Host -${*}.All[-1].Jobs -join ', ' | Write-Host -ForegroundColor Magenta -Write-Host diff --git a/DSC/Build/BuildHelpers/FlattenArray.ps1 b/DSC/Build/BuildHelpers/FlattenArray.ps1 deleted file mode 100644 index 8649a81c..00000000 --- a/DSC/Build/BuildHelpers/FlattenArray.ps1 +++ /dev/null @@ -1,8 +0,0 @@ -function FlattenArray -{ - param ( - [Parameter(Mandatory)] - [array]$InputObject - ) - ,@($InputObject | ForEach-Object { $_ }) -} \ No newline at end of file diff --git a/DSC/Build/BuildHelpers/Invoke-InternalPSDepend.ps1 b/DSC/Build/BuildHelpers/Invoke-InternalPSDepend.ps1 deleted file mode 100644 index f8eb6170..00000000 --- a/DSC/Build/BuildHelpers/Invoke-InternalPSDepend.ps1 +++ /dev/null @@ -1,38 +0,0 @@ -function Invoke-PSDependInternal { - param([Parameter(Mandatory)] - [hashtable]$PSDependParameters, - - [Parameter(Mandatory)] - [string]$Repository - ) - - if (-not $PSDependParameters.ContainsKey('Path')) { - Write-Error 'Path is missing in PSDependParameters' - return - } - - $psDependFilePath = $PSDependParameters.Path - if (-not (Test-Path -Path $psDependFilePath)) { - Write-Error "The path '$psDependFilePath' does not exist" - return - } - - $content = Get-Content -Path $psDependFilePath -Raw - $newString = "Repository = '$Repository'" - $content = $content -replace "Repository = 'PSGallery'", $newString - - $path = "$projectPath\PSDependTemp.psd1" - $content | Out-File $path -Force - $PSDependParameters.Path = $path - - try { - Invoke-PSDepend @PSDependParameters -ErrorAction Stop - } - catch { - Write-Error -ErrorRecord $_ - } - finally { - Remove-Item -Path $path -Force - } - -} \ No newline at end of file diff --git a/DSC/Build/BuildHelpers/Resolve-Dependency.ps1 b/DSC/Build/BuildHelpers/Resolve-Dependency.ps1 deleted file mode 100644 index c52ab0c4..00000000 --- a/DSC/Build/BuildHelpers/Resolve-Dependency.ps1 +++ /dev/null @@ -1,62 +0,0 @@ -function Resolve-Dependency { - [CmdletBinding()] - param() - - Write-Host 'Downloading dependencies, this may take a while' -ForegroundColor Green - if (-not (Get-PackageProvider -Name NuGet -ForceBootstrap)) { - $providerBootstrapParams = @{ - Name = 'nuget' - Force = $true - ForceBootstrap = $true - } - if ($PSBoundParameters.ContainsKey('Verbose')) { - $providerBootstrapParams.Add('Verbose', $Verbose) - } - if ($GalleryProxy) { - $providerBootstrapParams.Add('Proxy', $GalleryProxy) - } - $null = Install-PackageProvider @providerBootstrapParams - } - - if (-not (Get-Module -Name "$buildModulesPath\PSDepend" -ListAvailable -ErrorAction SilentlyContinue)) { - Write-Verbose -Message 'BootStrapping PSDepend' - Write-Verbose -Message "Parameter $buildOutput" - $installPSDependParams = @{ - Name = 'PSDepend' - Path = $buildModulesPath - Confirm = $false - } - if ($PSBoundParameters.ContainsKey('verbose')) { - $installPSDependParams.Add('Verbose', $Verbose) - } - if ($Repository) { - $installPSDependParams.Add('Repository', $Repository) - } - if ($GalleryProxy) { - $installPSDependParams.Add('Proxy', $GalleryProxy) - } - if ($GalleryCredential) { - $installPSDependParams.Add('ProxyCredential', $GalleryCredential) - } - - try { - Save-Module @installPSDependParams -ErrorAction Stop - } - catch { - Save-Module @installPSDependParams - } - } - - $psDependParams = @{ - Force = $true - Path = "$ProjectPath\PSDepend.Build.psd1" - #was removed in some private projects for unknown reason - #Target = $buildModulesPath - } - if ($PSBoundParameters.ContainsKey('Verbose')) { - $psDependParams.Add('Verbose', $Verbose) - } - Import-Module -Name PSDepend - Invoke-PSDependInternal -PSDependParameters $psDependParams -Repository $Repository - Write-Verbose 'Project Bootstrapped, returning to Invoke-Build' -} diff --git a/DSC/Build/BuildHelpers/Set-PSModulePath.ps1 b/DSC/Build/BuildHelpers/Set-PSModulePath.ps1 deleted file mode 100644 index 48e55cca..00000000 --- a/DSC/Build/BuildHelpers/Set-PSModulePath.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -function Set-PSModulePath { - param( - [String[]] - $ModuleToLeaveLoaded, - - [String[]] - $PathsToSet = @() - ) - - $env:PSModulePath = Join-Path -Path $PShome -ChildPath Modules - - Get-Module | Where-Object { $_.Name -notin $ModuleToLeaveLoaded } | Remove-Module -Force - - $PathsToSet.Foreach{ - if ($_ -notin ($env:PSModulePath -split ';')) { - $env:PSModulePath = "$_;$($Env:PSModulePath)" - } - } - -} \ No newline at end of file diff --git a/DSC/Build/BuildHelpers/Split-Array.ps1 b/DSC/Build/BuildHelpers/Split-Array.ps1 deleted file mode 100644 index 4514f745..00000000 --- a/DSC/Build/BuildHelpers/Split-Array.ps1 +++ /dev/null @@ -1,35 +0,0 @@ -function Split-Array -{ - param( - [Parameter(Mandatory)] - [System.Collections.IEnumerable]$List, - - [Parameter(Mandatory, ParameterSetName = 'ChunkSize')] - [int]$ChunkSize, - - [Parameter(Mandatory, ParameterSetName = 'ChunkCount')] - [int]$ChunkCount - ) - $aggregateList = @() - - if ($ChunkCount) - { - $ChunkSize = [Math]::Ceiling($List.Count / $ChunkCount) - } - - $blocks = [Math]::Floor($List.Count / $ChunkSize) - $leftOver = $List.Count % $ChunkSize - for ($i = 0; $i -lt $blocks; $i++) - { - $end = $ChunkSize * ($i + 1) - 1 - - $aggregateList += @(, $List[$start..$end]) - $start = $end + 1 - } - if ($leftOver -gt 0) - { - $aggregateList += @(, $List[$start..($end + $leftOver)]) - } - - , $aggregateList -} \ No newline at end of file diff --git a/DSC/Build/DscHelpers/Get-DatumNodesRecursive.ps1 b/DSC/Build/DscHelpers/Get-DatumNodesRecursive.ps1 deleted file mode 100644 index 19058c96..00000000 --- a/DSC/Build/DscHelpers/Get-DatumNodesRecursive.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -function Get-DatumNodesRecursive -{ - param( - [object[]]$Nodes, - - [int]$Depth - ) - - if ($Depth -gt 0) - { - $expandedNodes = foreach ($node in $Nodes) - { - foreach ($propertyName in ($node.PSObject.Properties | Where-Object MemberType -eq 'ScriptProperty').Name) - { - $node | ForEach-Object { - $newNode = $_."$propertyName" - if ($newNode -is [System.Collections.IDictionary]) { - if (-not $newNode.Contains('Name')) { - if ($propertyName -eq 'AllNodes') { - $newNode.Add('Name', '*') - } - else { - $newNode.Add('Name', $propertyName) - } - } - - [hashtable]$newNode - } - else - { - $newNode - } - } - } - } - - if ($expandedNodes) - { - $expandedNodes = FlattenArray -InputObject $expandedNodes - $Depth-- - $expandedNodes | Where-Object { $_ -is [System.Collections.IDictionary] } - Get-DatumNodesRecursive -Nodes $expandedNodes -Depth $Depth - } - else - { - $Depth = 0 - } - } -} diff --git a/DSC/Build/DscHelpers/Get-DscErrorMessage.ps1 b/DSC/Build/DscHelpers/Get-DscErrorMessage.ps1 deleted file mode 100644 index 4d9d3ac2..00000000 --- a/DSC/Build/DscHelpers/Get-DscErrorMessage.ps1 +++ /dev/null @@ -1,44 +0,0 @@ -function Get-DscErrorMessage { - Param( - [System.Exception] - $Exception - ) - - switch ($Exception) { - { $_ -is [System.Management.Automation.ItemNotFoundException] } { - #can be ignored, very likely caused by Get-Item within the PSDesiredStateConfiguration module - break - } - { $_.Message -match "Unable to find repository 'PSGallery" } { - 'Error in Package Management' - break - } - - { $_.Message -match 'A second CIM class definition'} { - # This happens when several versions of same module are available. - # Mainly a problem when when $Env:PSModulePath is polluted or - # DscResources or DSC_Configuration are not clean - 'Multiple version of the same module exist' - break - } - { $_ -is [System.Management.Automation.ParentContainsErrorRecordException]} { - "Compilation Error: $_.Message" - break - } - { $_.Message -match ([regex]::Escape("Cannot find path 'HKLM:\SOFTWARE\Microsoft\Powershell\3\DSC'")) } { - if ($_.InvocationInfo.PositionMessage -match 'PSDscAllowDomainUser') { - # This tend to be repeated for all nodes even if only 1 is affected - 'Domain user credentials are used and PSDscAllowDomainUser is not set' - break - } - elseif ($_.InvocationInfo.PositionMessage -match 'PSDscAllowPlainTextPassword') { - "It is not recommended to use plain text password. Use PSDscAllowPlainTextPassword = `$false" - break - } - else { - #can be ignored - break - } - } - } -} diff --git a/DSC/Build/DscHelpers/Get-FilteredConfigurationData.ps1 b/DSC/Build/DscHelpers/Get-FilteredConfigurationData.ps1 deleted file mode 100644 index 3b4ea1c9..00000000 --- a/DSC/Build/DscHelpers/Get-FilteredConfigurationData.ps1 +++ /dev/null @@ -1,44 +0,0 @@ -function Get-FilteredConfigurationData { - param( - [ScriptBlock] - $Filter = {}, - - [int] - $CurrentJobNumber, - - [int] - $TotalJobCount = 1, - - $Datum = $(Get-variable Datum -ValueOnly -ErrorAction Stop) - ) - - #even if default value is assiged to the Filter parameter, it is sometimes $null - if ($Filter -eq $null) { - $Filter = {} - } - - $allNodes = @(Get-DatumNodesRecursive -Nodes $Datum.AllNodes -Depth 20) - $totalNodeCount = $allNodes.Count - - Write-Host "Node count: $($allNodes.Count)" - - if($Filter.ToString() -ne ([System.Management.Automation.ScriptBlock]::Create({})).ToString()) { - Write-Host "Filter: $($Filter.ToString())" - $allNodes = [System.Collections.Hashtable[]]$allNodes.Where($Filter) - Write-Host "Node count after applying filter: $($allNodes.Count)" - } - - if (-not $allNodes.Count) - { - Write-Error "No node data found. There are in total $totalNodeCount nodes defined, but no node was selected. You may want to verify the filter: '$Filter'." - } - - $CurrentJobNumber-- - $allNodes = Split-Array -List $allNodes -ChunkCount $TotalJobCount - $allNodes = $allNodes[$CurrentJobNumber] - - return @{ - AllNodes = $allNodes - Datum = $Datum - } -} diff --git a/DSC/Build/DscMofHelpers/Copy-DscMof.ps1 b/DSC/Build/DscMofHelpers/Copy-DscMof.ps1 deleted file mode 100644 index 22f6c03a..00000000 --- a/DSC/Build/DscMofHelpers/Copy-DscMof.ps1 +++ /dev/null @@ -1,37 +0,0 @@ -function Copy-DscMof -{ - param( - [Parameter(Mandatory)] - [string]$MofPath, - - [Parameter(Mandatory)] - [string]$Environment, - - [Parameter(Mandatory)] - [string]$TargetPath - ) - - if (-not (Test-Path -Path $MofPath)) { - Write-Error "The MOF file '$MofPath' cannot be found." - return - } - if (-not (Test-Path -Path $TargetPath)) { - Write-Error "The MOF file '$TargetPath' cannot be found." - return - } - - $mofFiles = dir -Path "$MofPath\*" -Include *.mof - if (-not $mofFiles) - { - Write-Error "No Mof files found in directory '$MofPath'" - return - } - - foreach ($mofFile in $mofFiles) - { - if (($mofFile | Get-DscMofEnvironment) -eq $Environment) { - Copy-Item -Path $mofFile.FullName -Destination $TargetPath -Force - Copy-Item -Path "$($mofFile.FullName).checksum" -Destination $TargetPath -Force - } - } -} \ No newline at end of file diff --git a/DSC/Build/DscMofHelpers/GetDscMofTagging.ps1 b/DSC/Build/DscMofHelpers/GetDscMofTagging.ps1 deleted file mode 100644 index d43ddd1c..00000000 --- a/DSC/Build/DscMofHelpers/GetDscMofTagging.ps1 +++ /dev/null @@ -1,66 +0,0 @@ -function Get-DscMofVersion { - param( - [Parameter(Mandatory, ValueFromPipeline)] - [string]$Path - ) - - process { - if (-not (Test-Path -Path $Path)) { - Write-Error "The MOF file '$Path' cannot be found." - return - } - - $content = Get-Content -Path $Path - - $xRegistryDscVersion = $content | Select-String -Pattern '\[xRegistry\]DscVersion' -Context 0, 10 - if (-not $xRegistryDscVersion) { - Write-Error "No version information found in MOF file '$Path'. The version information must be added using the 'xRegistry' named 'DscVersion'." - return - } - - $valueData = $xRegistryDscVersion.Context.PostContext | Select-String -Pattern 'ValueData' -Context 0, 1 - if (-not $valueData) { - Write-Error "Found the resource 'xRegistry' named 'DscVersion' in '$Path' but no ValueData in the expected range (10 lines after defining '[xRegistry]DscVersion'." - return - } - - try { - $value = $valueData.Context.PostContext[0].Trim().Replace('"', '') - [System.Version]$value - } - catch { - Write-Error "ValueData could not be converted into 'System.Version'. The value taken from the MOF file was '$value'" - return - } - } -} - -function Get-DscMofEnvironment { - param( - [Parameter(Mandatory, ValueFromPipeline)] - [string]$Path - ) - - process { - if (-not (Test-Path -Path $Path)) { - Write-Error "The MOF file '$Path' cannot be found." - return - } - - $content = Get-Content -Path $Path - - $xRegistryDscEnvironment = $content | Select-String -Pattern '\[xRegistry\]DscEnvironment' -Context 0, 10 - if (-not $xRegistryDscEnvironment) { - Write-Error "No environment information found in MOF file '$Path'. The environment information must be added using the 'xRegistryx' named 'DscEnvironment'." - return - } - - $valueData = $xRegistryDscEnvironment.Context.PostContext | Select-String -Pattern 'ValueData' -Context 0, 1 - if (-not $valueData) { - Write-Error "Found the resource 'xRegistry' named 'DscEnvironment' in '$Path' but no ValueData in the expected range (10 lines after defining '[xRegistry]DscEnvironment'." - return - } - - $valueData.Context.PostContext[0].Trim().Replace('"', '') - } -} \ No newline at end of file diff --git a/DSC/Build/Tasks/CleanBuildOutput.ps1 b/DSC/Build/Tasks/CleanBuildOutput.ps1 deleted file mode 100644 index c8bdcccd..00000000 --- a/DSC/Build/Tasks/CleanBuildOutput.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -param ( - [System.IO.DirectoryInfo] - $ProjectPath = (property ProjectPath $BuildRoot), - - [String] - $BuildOutput = (property BuildOutput 'C:\BuildOutput'), - - [String] - $LineSeparation = (property LineSeparation ('-' * 78)) -) - -task CleanBuildOutput { - # Synopsis: Clears the BuildOutput folder from its artefacts, but leaves the modules subfolder and its content. - - if (-not [System.IO.Path]::IsPathRooted($BuildOutput)) - { - $BuildOutput = Join-Path -Path $ProjectPath.FullName -ChildPath $BuildOutput - } - if (Test-Path $BuildOutput) - { - Write-Host "Removing $BuildOutput\*" - Get-ChildItem -Path $BuildOutput -Exclude Modules | Remove-Item -Force -Recurse - } -} - -task CleanModule { - # Synopsis: Clears the content of the BuildOutput folder INCLUDING the modules folder - if (-not [System.IO.Path]::IsPathRooted($BuildOutput)) - { - $BuildOutput = Join-Path -Path $ProjectPath.FullName -ChildPath $BuildOutput - } - Write-Host "Removing $BuildOutput\*" - Get-ChildItem -Path .\BuildOutput\ | Remove-Item -Force -Recurse -Verbose -ErrorAction Stop -} diff --git a/DSC/Build/Tasks/CleanDscConfigurationsFolder.ps1 b/DSC/Build/Tasks/CleanDscConfigurationsFolder.ps1 deleted file mode 100644 index 1dc44936..00000000 --- a/DSC/Build/Tasks/CleanDscConfigurationsFolder.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -task CleanDscConfigurationsFolder { - Get-ChildItem -Path "$ConfigurationsFolder" -Recurse | Remove-Item -Force -Recurse -Exclude README.md -} diff --git a/DSC/Build/Tasks/CleanDscResourcesFolder.ps1 b/DSC/Build/Tasks/CleanDscResourcesFolder.ps1 deleted file mode 100644 index 1da8b5d4..00000000 --- a/DSC/Build/Tasks/CleanDscResourcesFolder.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -task CleanDscResourcesFolder { - Get-ChildItem -Path "$ResourcesFolder" -Recurse | Remove-Item -Force -Recurse -Exclude README.md -} diff --git a/DSC/Build/Tasks/CompileDatumRsop.ps1 b/DSC/Build/Tasks/CompileDatumRsop.ps1 deleted file mode 100644 index 01789bc2..00000000 --- a/DSC/Build/Tasks/CompileDatumRsop.ps1 +++ /dev/null @@ -1,38 +0,0 @@ -param ( - [string] - $RsopFolder = (property RsopFolder 'RSOP') -) - -task CompileDatumRsop { - - Start-Transcript -Path "$BuildOutput\Logs\CompileDatumRsop.log" - - try { - $rsopOutputPath = if (-not [System.IO.Path]::IsPathRooted($RsopFolder)) { - Join-Path -Path $BuildOutput -ChildPath $RsopFolder - } - else { - $RsopFolder - } - if (-not (Test-Path -Path $rsopOutputPath)) { - mkdir -Path $rsopOutputPath -Force | Out-Null - } - - if ($configurationData.AllNodes) { - Write-Build Green "Generating RSOP output for $($configurationData.AllNodes.Count) nodes..." - $configurationData.AllNodes | - Where-Object Name -ne * | - ForEach-Object { - Write-Build Green "`t$($_.Name)" - $nodeRsop = Get-DatumRsop -Datum $datum -AllNodes ([ordered]@{ } + $_) - $nodeRsop | ConvertTo-Json -Depth 40 | ConvertFrom-Json | Convertto-Yaml -OutFile (Join-Path -Path $rsopOutputPath -ChildPath "$($_.Name).yml") -Force - } - } - else { - Write-Build Green "No data for generating RSOP output." - } - } - finally { - Stop-Transcript - } -} diff --git a/DSC/Build/Tasks/CompileRootConfiguration.ps1 b/DSC/Build/Tasks/CompileRootConfiguration.ps1 deleted file mode 100644 index 49bbea5f..00000000 --- a/DSC/Build/Tasks/CompileRootConfiguration.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -task CompileRootConfiguration { - - Start-Transcript -Path "$BuildOutput\Logs\CompileRootConfiguration.log" - - if (-not (Test-Path -Path $BuildOutput\MOF)) { - mkdir -Path $BuildOutput\MOF | Out-Null - } - - try { - $mofs = . (Join-Path -Path $ProjectPath -ChildPath 'RootConfiguration.ps1') - if ($ConfigurationData.AllNodes.Count -ne $mofs.Count) { - Write-Warning "Compiled MOF file count <> node count" - } - Write-Build Green "Successfully compiled $($mofs.Count) MOF files" - } - catch { - Write-Build Red "ERROR OCCURED DURING COMPILATION: $($_.Exception.Message)" - $relevantErrors = $Error | Where-Object { - $_.Exception -isnot [System.Management.Automation.ItemNotFoundException] - } - $relevantErrors[0..2] | Out-String | ForEach-Object { Write-Warning $_ } - } - finally { - Stop-Transcript - } - -} diff --git a/DSC/Build/Tasks/CompileRootMetaMof.ps1 b/DSC/Build/Tasks/CompileRootMetaMof.ps1 deleted file mode 100644 index da56f9c0..00000000 --- a/DSC/Build/Tasks/CompileRootMetaMof.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -task CompileRootMetaMof { - - if (-not (Test-Path -Path $BuildOutput\MetaMof)) { - mkdir -Path $BuildOutput\MetaMof | Out-Null - } - - if ($configurationData.AllNodes) { - . (Join-Path -Path $ProjectPath -ChildPath RootMetaMof.ps1) - $metaMofs = RootMetaMOF -ConfigurationData $configurationData -OutputPath (Join-Path -Path $BuildOutput -ChildPath MetaMof) - Write-Build Green "Successfully compiled $($metaMofs.Count) Meta MOF files." - } - else { - Write-Build Green "No data to compile Meta MOF files" - } -} \ No newline at end of file diff --git a/DSC/Build/Tasks/CompressModulesWithChecksum.ps1 b/DSC/Build/Tasks/CompressModulesWithChecksum.ps1 deleted file mode 100644 index 7a69f2a4..00000000 --- a/DSC/Build/Tasks/CompressModulesWithChecksum.ps1 +++ /dev/null @@ -1,50 +0,0 @@ -task CompressModulesWithChecksum { - - if ($SkipCompressedModulesBuild) - { - Write-Host 'Skipping preparation of Compressed Modules as $SkipCompressedModulesBuild is set' - return - } - - Start-Transcript -Path "$BuildOutput\Logs\CompressModulesWithChecksum.log" - - try { - - if (-not (Test-Path -Path $BuildOutput\CompressedModules)) { - mkdir -Path $BuildOutput\CompressedModules | Out-Null - } - - if ($SkipCompressedModulesBuild) - { - Write-Host "Skipping preparation of Compressed Modules as '`$SkipCompressedModulesBuild' is set" - return - } - if ($configurationData.AllNodes -and $CurrentJobNumber -eq 1) { - - $modules = Get-ModuleFromFolder -ModuleFolder "$ProjectPath\DscResources\" - $compressedModulesPath = "$BuildOutput\CompressedModules" - - foreach ($module in $modules) { - $destinationPath = Join-Path -Path $compressedModulesPath -ChildPath "$($module.Name)_$($module.Version).zip" - Compress-Archive -Path "$($module.ModuleBase)\*" -DestinationPath $destinationPath - $hash = (Get-FileHash -Path $destinationPath).Hash - - try { - $stream = New-Object -TypeName System.IO.StreamWriter("$destinationPath.checksum", $false) - [void] $stream.Write($hash) - } - finally { - if ($stream) { - $stream.Close() - } - } - } - } - else { - Write-Build Green "No data in 'ConfigurationData.AllNodes', skipping task 'CompressModulesWithChecksum'." - } - } - finally { - Stop-Transcript - } -} diff --git a/DSC/Build/Tasks/Deploy.ps1 b/DSC/Build/Tasks/Deploy.ps1 deleted file mode 100644 index 6179593a..00000000 --- a/DSC/Build/Tasks/Deploy.ps1 +++ /dev/null @@ -1,44 +0,0 @@ -Task Deploy { - - if (-not ([System.IO.Path]::IsPathRooted($BuildOutput))) { - $BuildOutput = Join-Path -Path $ProjectPath -ChildPath $BuildOutput - } - - Write-Host "Starting deployment with files inside '$BuildOutput'" - - if ($env:BHBuildSystem -eq 'AppVeyor') { - $artifactsPath = "$BuildOutput\CompressedArtifacts" - if (-not (Test-Path -Path $artifactsPath)) { - mkdir -Path $artifactsPath | Out-Null - } - - Compress-Archive -Path $BuildOutput\MOF -DestinationPath "$BuildOutput\CompressedArtifacts\MOF.zip" -Force - Compress-Archive -Path $BuildOutput\MetaMOF -DestinationPath "$BuildOutput\CompressedArtifacts\MetaMOF.zip" -Force - Compress-Archive -Path $BuildOutput\RSOP -DestinationPath "$BuildOutput\CompressedArtifacts\RSOP.zip" -Force - Compress-Archive -Path $BuildOutput\CompressedModules -DestinationPath "$BuildOutput\CompressedArtifacts\CompressedModules.zip" -Force - - Push-AppVeyorArtifact "$BuildOutput\CompressedArtifacts\MOF.zip" -FileName MOF.zip -DeploymentName MOF - Push-AppVeyorArtifact "$BuildOutput\CompressedArtifacts\MetaMOF.zip" -FileName MetaMOF.zip -DeploymentName MetaMOF - Push-AppVeyorArtifact "$BuildOutput\CompressedArtifacts\RSOP.zip" -FileName RSOP.zip -DeploymentName RSOP - Push-AppVeyorArtifact "$BuildOutput\CompressedArtifacts\CompressedModules.zip" -FileName CompressedModules.zip -DeploymentName CompressedModules - } - elseif ($env:BUILD_REPOSITORY_PROVIDER -eq 'TfsGit' -and $env:RELEASE_ENVIRONMENTNAME) { - Write-Host "Source Branch Name is: '$($env:BUILD_SOURCEBRANCHNAME )'" - Write-Host "Release environment Name is : '$($env:RELEASE_ENVIRONMENTNAME)'" - - if ($env:BUILD_SOURCEBRANCHNAME -eq 'dev' -and $env:RELEASE_ENVIRONMENTNAME -ne 'Dev') { - Write-Host 'Dev branch should be only deployed to Dev environment' - return - } - - Copy-DscMof -MofPath "$BuildOutput\MOF" -TargetPath $env:DscConfiguration -Environment $env:RELEASE_ENVIRONMENTNAME - Copy-DscMof -MofPath "$BuildOutput\MOF" -TargetPath $env:DscConfiguration -Environment $env:RELEASE_ENVIRONMENTNAME - if (Test-Path -Path "$BuildOutput\CompressedModules\*") { - Copy-Item -Path "$BuildOutput\CompressedModules\*" -Destination $env:DscModules - } - else { - Write-Host "The folder '$BuildOutput\CompressedModules\*' does not exist, skipping deployment of CompressedModules." - } - } - -} \ No newline at end of file diff --git a/DSC/Build/Tasks/DownloadDependencies.ps1 b/DSC/Build/Tasks/DownloadDependencies.ps1 deleted file mode 100644 index 7618ee73..00000000 --- a/DSC/Build/Tasks/DownloadDependencies.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -task DownloadDependencies -if ($DownloadResourcesAndConfigurations -or $Tasks -contains 'DownloadDependencies') DownloadDscConfigurations, DownloadDscResources -Before SetPsModulePath - -task DownloadDscResources { - $PSDependResourceDefinition = "$ProjectPath\PSDepend.DscResources.psd1" - if (Test-Path $PSDependResourceDefinition) { - $psDependParams = @{ - Path = $PSDependResourceDefinition - Confirm = $false - Target = $resourcePath - } - Invoke-PSDependInternal -PSDependParameters $psDependParams -Repository $Repository - } -} diff --git a/DSC/Build/Tasks/DownloadDscConfigurations.ps1 b/DSC/Build/Tasks/DownloadDscConfigurations.ps1 deleted file mode 100644 index b5008db2..00000000 --- a/DSC/Build/Tasks/DownloadDscConfigurations.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -task DownloadDscConfigurations { - $PSDependConfigurationDefinition = "$ProjectPath\PSDepend.DscConfigurations.psd1" - if (Test-Path $PSDependConfigurationDefinition) { - Write-Build Green 'Pull dependencies from PSDepend.DscConfigurations.psd1' - $psDependParams = @{ - Path = $PSDependConfigurationDefinition - Confirm = $false - Target = $configurationPath - } - Invoke-PSDependInternal -PSDependParameters $psDependParams -Repository $Repository - } -} diff --git a/DSC/Build/Tasks/Init.ps1 b/DSC/Build/Tasks/Init.ps1 deleted file mode 100644 index d3cbf181..00000000 --- a/DSC/Build/Tasks/Init.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -Task Init { - - if ($PSVersionTable.PSEdition -ne 'Desktop') { - Write-Error "The build script required Windows PowerShell 5.1 to work" - } - - if (-not $env:BHProjectName) { - try { - Write-Host "Calling 'Set-BuildEnvironment' with path '$ProjectPath'" - Set-BuildEnvironment -Path $ProjectPath - } - catch { - Write-Host "Error calling 'Set-BuildEnvironment'." - throw $_ - } - } - - $global:Filter = $null - - $lines - Set-Location -Path $ProjectPath - "Build System Details:" - Get-Item -Path env:BH* - "`n" - -} diff --git a/DSC/Build/Tasks/LoadDatumConfigData.ps1 b/DSC/Build/Tasks/LoadDatumConfigData.ps1 deleted file mode 100644 index b36068f2..00000000 --- a/DSC/Build/Tasks/LoadDatumConfigData.ps1 +++ /dev/null @@ -1,22 +0,0 @@ -task LoadDatumConfigData { - - Import-Module -Name PowerShell-Yaml -Scope Global - Import-Module -Name Datum -Scope Global - - $global:node = $null #very imporant, otherwise the 2nd build in the same session won't work - $datumDefinitionFile = Join-Path -Resolve -Path $configDataPath -ChildPath 'Datum.yml' - Write-Build Green "Loading Datum Definition from '$datumDefinitionFile'" - $global:datum = New-DatumStructure -DefinitionFile $datumDefinitionFile - if (-not ($datum.AllNodes)) { - Write-Error 'No nodes found in the solution' - } - - if ($env:BHCommitMessage -match "--Added new node '(?\w+)'") - { - $global:Filter = $Filter = [scriptblock]::Create('$_.NodeName -eq "{0}"' -f $Matches.NodeName) - $global:SkipCompressedModulesBuild = $true - } - - $global:configurationData = Get-FilteredConfigurationData -Filter $Filter -CurrentJobNumber $CurrentJobNumber -TotalJobCount $TotalJobCount - -} diff --git a/DSC/Build/Tasks/NewMofChecksums.ps1 b/DSC/Build/Tasks/NewMofChecksums.ps1 deleted file mode 100644 index 7b24bf81..00000000 --- a/DSC/Build/Tasks/NewMofChecksums.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -task NewMofChecksums { - $mofs = Get-ChildItem -Path (Join-Path -Path $BuildOutput -ChildPath MOF) -ErrorAction SilentlyContinue - foreach ($mof in $mofs) - { - if ($mof.BaseName -in $global:configurationData.AllNodes.NodeName) - { - New-DscChecksum -Path $mof.FullName -Verbose:$false -Force - } - } -} \ No newline at end of file diff --git a/DSC/Build/Tasks/SetPsModulePath.ps1 b/DSC/Build/Tasks/SetPsModulePath.ps1 deleted file mode 100644 index 13ba9a21..00000000 --- a/DSC/Build/Tasks/SetPsModulePath.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -param ( - [string[]] - $ModuleToLeaveLoaded = (property ModuleToLeaveLoaded @('InvokeBuild', - 'PSReadline', - 'PackageManagement', - 'ISESteroids', - 'PowerShellGet', - 'PowerShellEditorServices.Commands', - 'PowerShellEditorServices.VSCode', - 'Get-DatumNodesRecursive', - 'Get-FilteredConfigurationData' - ) - ) -) -task SetPsModulePath { - if (-not ([System.IO.Path]::IsPathRooted($BuildOutput))) { - $BuildOutput = Join-Path -Path $ProjectPath -ChildPath $BuildOutput - } - - $configurationPath = Join-Path -Path $ProjectPath -ChildPath $ConfigurationsFolder - $resourcePath = Join-Path -Path $ProjectPath -ChildPath $ResourcesFolder - $buildModulesPath = Join-Path -Path $BuildOutput -ChildPath Modules - - $pathToSet = $buildModulesPath, $resourcePath, $configurationPath - if ($env:BHBuildSystem -eq 'AppVeyor') { - $pathToSet += ';C:\Program Files\AppVeyor\BuildAgent\Modules' - } - - Set-PSModulePath -ModuleToLeaveLoaded $moduleToLeaveLoaded -PathsToSet $pathToSet - - "`n" - 'PSModulePath:' - $env:PSModulePath -split ';' - "`n" - -} \ No newline at end of file diff --git a/DSC/Build/Tasks/TestBuildAcceptance.ps1 b/DSC/Build/Tasks/TestBuildAcceptance.ps1 deleted file mode 100644 index cc432e92..00000000 --- a/DSC/Build/Tasks/TestBuildAcceptance.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -task TestBuildAcceptance { - - if (-not (Test-Path -Path $testsPath)) { - Write-Build Yellow "Path for tests '$testsPath' does not exist" - return - } - - if (-not ([System.IO.Path]::IsPathRooted($BuildOutput))) { - $BuildOutput = Join-Path -Path $PSScriptRoot -ChildPath $BuildOutput - } - - if ($env:BHBuildSystem -in 'AppVeyor', 'Unknown') { - #AppVoyor build are not deploying to a pull server yet. - $excludeTag = 'PullServer' - } - - $testResultsPath = Join-Path -Path $BuildOutput -ChildPath BuildAcceptanceTestResults.xml - Write-Host "testResultsPath is: $testResultsPath" - Write-Host "testsPath is: $testsPath" - Write-Host "BuildOutput is: $BuildOutput" - - $pesterParams = @{ - Script = $testsPath - OutputFile = $testResultsPath - OutputFormat = 'NUnitXml' - Tag = 'BuildAcceptance' - PassThru = $true - Show = 'Failed', 'Summary' - } - if ($excludeTag) { - $pesterParams.ExcludeTag = $excludeTag - } - $testResults = Invoke-Pester @pesterParams - - #if the build is invoked locally or or an unknown build system, it should fail hard if the - #test result contains errors. Otherwise we leave if up to the build system to handle the error. - if ($env:BHBuildSystem -eq 'Unknown') - { - assert (-not $testResults.FailedCount) - } - -} diff --git a/DSC/Build/Tasks/TestConfigData.ps1 b/DSC/Build/Tasks/TestConfigData.ps1 deleted file mode 100644 index 1d28b7ef..00000000 --- a/DSC/Build/Tasks/TestConfigData.ps1 +++ /dev/null @@ -1,21 +0,0 @@ -task TestConfigData { - - if (-not (Test-Path -Path $testsPath)) { - Write-Build Yellow "Path for tests '$testsPath' does not exist" - return - } - - if (-not ([System.IO.Path]::IsPathRooted($BuildOutput))) { - $BuildOutput = Join-Path -Path $PSScriptRoot -ChildPath $BuildOutput - } - - $testResultsPath = Join-Path -Path $BuildOutput -ChildPath IntegrationTestResults.xml - Write-Host "testResultsPath is: $testResultsPath" - Write-Host "testsPath is: $testsPath" - Write-Host "BuildOutput is: $BuildOutput" - - $testResults = Invoke-Pester -Script "$testsPath\ConfigData" -PassThru -OutputFile $testResultsPath -OutputFormat NUnitXml -Tag Integration -Show Failed, Summary - - assert ($testResults.FailedCount -eq 0) - -} diff --git a/DSC/Build/Tasks/TestDscResources.ps1 b/DSC/Build/Tasks/TestDscResources.ps1 deleted file mode 100644 index 9e91d289..00000000 --- a/DSC/Build/Tasks/TestDscResources.ps1 +++ /dev/null @@ -1,63 +0,0 @@ -task TestDscResources { - - try { - Start-Transcript -Path "$BuildOutput\Logs\TestDscResources.log" - - foreach ($configModule in (Get-Dependency -Path $ProjectPath/PSDepend.DscConfigurations.psd1).DependencyName) { - Write-Host ------------------------------------------------------------ - Write-Host 'Currently loaded modules:' - $env:PSModulePath -split ';' | Write-Host - Write-Host ------------------------------------------------------------ - Write-Host "The '$configModule' module provides the following configurations (DSC Composite Resources)" - $m = Get-Module -Name $configModule -ListAvailable - if (-not $m) { - Write-Error "The module '$configModule' containing the configurations could not be found. Please check the file 'PSDepend.DscConfigurations.psd1' and verify if the module is available in the given repository" -ErrorAction Stop - } - $resources = dir -Path "$($m.ModuleBase)\DscResources" - $resourceCount = $resources.Count - Write-Host "ResourceCount $resourceCount" - - $maxIterations = 5 - while ($resourceCount -ne (Get-DscResource -Module $configModule).Count -and $maxIterations -gt 0) { - $dscResources = Get-DscResource -Module $configModule - Write-Host "ResourceCount DOES NOT match, currently '$($dscResources.Count)'. Resources missing:" - Write-Host (Compare-Object -ReferenceObject $resources.Name -DifferenceObject $dscResources.Name).InputObject - Start-Sleep -Seconds 5 - $maxIterations-- - } - if ($maxIterations -eq 0) { - throw 'Could not get the expected DSC Resource count' - } - - Write-Host "ResourceCount matches ($resourceCount)" - Write-Host ------------------------------------------------------------ - Write-Host 'Known DSC Composite Resources' - Write-Host ------------------------------------------------------------ - Get-DscResource -Module $configModule | Out-String | Write-Host - - Write-Host ------------------------------------------------------------ - Write-Host 'Known DSC Resources' - Write-Host ------------------------------------------------------------ - Write-Host - Import-LocalizedData -BindingVariable requiredResources -FileName PSDepend.DscResources.psd1 -BaseDirectory $ProjectPath - $requiredResources = @($requiredResources.GetEnumerator() | Where-Object { $_.Name -ne 'PSDependOptions' }) - $requiredResources.GetEnumerator() | Foreach-Object { - $rr = $_ - try { - Get-DscResource -Module $rr.Name -WarningAction Stop - } - catch { - Write-Error "DSC Resource '$($rr.Name)' cannot be found" -ErrorAction Stop - } - } | Group-Object -Property ModuleName, Version | - Select-Object -Property Name, Count | Write-Host - Write-Host ------------------------------------------------------------ - } - } - catch { - Write-Error -ErrorRecord $_ - } - finally { - Stop-Transcript - } -} diff --git a/DSC/Build/Tasks/VersionControl.ps1 b/DSC/Build/Tasks/VersionControl.ps1 deleted file mode 100644 index 0ac0fceb..00000000 --- a/DSC/Build/Tasks/VersionControl.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -task VersionControl { - - if ($env:BHBuildSystem -in 'VSTS', 'Azure Pipelines', 'AppVeyor') { - $path = "$ProjectPath\DscConfigData\Baselines\DscLcm.yml" - - $content = Select-String -Pattern 'DscTagging:' -Path $path -Context 0,1 - $content.Context.PostContext[0] -match ' Version: (?\d+.\d+.\d+)' | Out-Null - - $version = [System.Version]$Matches.Version - $version = New-Object System.Version($version.Major, $version.Minor, $env:BHBuildNumber) - - $content = Get-Content -Path $path -Raw - $content = $content -replace $Matches.Version, $version - $content | Set-Content -Path $path - } - -} diff --git a/DSC/DSC.psd1 b/DSC/DSC.psd1 deleted file mode 100644 index 5915ab11..00000000 --- a/DSC/DSC.psd1 +++ /dev/null @@ -1 +0,0 @@ -#required for BuildHelpers, don't remove the file if you use Azure Pipelines diff --git a/DSC/PSDepend.Build.psd1 b/DSC/PSDepend.Build.psd1 deleted file mode 100644 index 9b36b565..00000000 --- a/DSC/PSDepend.Build.psd1 +++ /dev/null @@ -1,32 +0,0 @@ -@{ - PSDependOptions = @{ - AddToPath = $true - Target = 'BuildOutput\Modules' - DependencyType = 'PSGalleryModule' - Parameters = @{ - Repository = 'PSGallery' - } - } - - InvokeBuild = 'latest' - BuildHelpers = 'latest' - Pester = '4.10.1' - PSScriptAnalyzer = 'latest' - DscBuildHelpers = 'latest' - Datum = '0.39.0' - 'powershell-yaml' = 'latest' - ProtectedData = 'latest' - 'Datum.ProtectedData' = 'latest' - 'Datum.InvokeCommand' = 'latest' - xDscResourceDesigner = 'latest' - ReverseDSC = 'latest' - Plaster = 'latest' - PowerShellForGitHub = 'latest' - 'Sampler.GitHubTasks' = 'latest' - Sampler = 'latest' - ChangelogManagement = 'latest' - ModuleBuilder = 'latest' - Configuration = 'latest' - Metadata = 'latest' - -} diff --git a/DSC/PSDepend.DscConfigurations.psd1 b/DSC/PSDepend.DscConfigurations.psd1 deleted file mode 100644 index 259a02dc..00000000 --- a/DSC/PSDepend.DscConfigurations.psd1 +++ /dev/null @@ -1,13 +0,0 @@ -@{ - PSDependOptions = @{ - AddToPath = $true - Target = 'DscConfigurations' - DependencyType = 'PSGalleryModule' - Parameters = @{ - Repository = 'PSGallery' - } - } - - CommonTasks = '0.3.259' - -} diff --git a/DSC/PostBuild.ps1 b/DSC/PostBuild.ps1 deleted file mode 100644 index 495efc11..00000000 --- a/DSC/PostBuild.ps1 +++ /dev/null @@ -1,62 +0,0 @@ -[CmdletBinding()] -param ( - [string] - $BuildOutput = 'BuildOutput', - - [string] - $ResourcesFolder = 'DscResources', - - [string] - $ConfigDataFolder = 'DscConfigData', - - [string] - $ConfigurationsFolder = 'DscConfigurations', - - [Parameter(Position = 0)] - $Tasks, - - [string] - $ProjectPath, - - [ScriptBlock] - $TaskHeader = { - Param($Path) - '' - '=' * 79 - Write-Build Cyan "`t`t`t$($Task.Name.Replace('_',' ').ToUpper())" - Write-Build DarkGray "$(Get-BuildSynopsis $Task)" - '-' * 79 - Write-Build DarkGray " $Path" - Write-Build DarkGray " $($Task.InvocationInfo.ScriptName):$($Task.InvocationInfo.ScriptLineNumber)" - '' - } -) - -#cannot be a default parameter value due to https://github.com/PowerShell/PowerShell/issues/4688 -if (-not $ProjectPath) { - $ProjectPath = $PSScriptRoot -} - -if (-not ([System.IO.Path]::IsPathRooted($BuildOutput))) { - $BuildOutput = Join-Path -Path $ProjectPath -ChildPath $BuildOutput -} - -$configurationPath = Join-Path -Path $ProjectPath -ChildPath $ConfigurationsFolder -$resourcePath = Join-Path -Path $ProjectPath -ChildPath $ResourcesFolder -$configDataPath = Join-Path -Path $ProjectPath -ChildPath $ConfigDataFolder -$testsPath = Join-Path -Path $ProjectPath -ChildPath $TestFolder - -#importing all resources from 'Build' directory -Get-ChildItem -Path "$PSScriptRoot/Build/" -Recurse -Include *.ps1 | - ForEach-Object { - Write-Verbose "Importing file $($_.BaseName)" - try { - . $_.FullName - } - catch { } -} - -task . NewMofChecksums, -CompressModulesWithChecksum, -Deploy, -TestBuildAcceptance diff --git a/DSC/RSOP.ps1 b/DSC/RSOP.ps1 deleted file mode 100644 index 9cea7c86..00000000 --- a/DSC/RSOP.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -$here = $PSScriptRoot -Import-Module ProtectedData -Import-Module powershell-yaml -$m = Import-Module Datum -PassThru - -$datumDefinitionFile = Join-Path -Path $here -ChildPath DscConfigData\Datum.yml -$nodeDefinitions = Get-ChildItem -Path $here\DscConfigData\AllNodes -Recurse -Include *.yml -$environments = (Get-ChildItem -Path $here\DscConfigData\AllNodes -Directory).BaseName -$roleDefinitions = Get-ChildItem -Path $here\DscConfigData\Roles -Recurse -Include *.yml - -& $m { $script:FileProviderDataCache = $null } -$datum = New-DatumStructure -DefinitionFile $datumDefinitionFile -$h = $datum.AllNodes.ToHashTable() - -$rsop = Get-DatumRsop -Datum $datum -AllNodes $datum.AllNodes.Dev -$rsop diff --git a/DSC/Tests/Acceptance/TestMofFiles.Tests.ps1 b/DSC/Tests/Acceptance/TestMofFiles.Tests.ps1 deleted file mode 100644 index 7bc05192..00000000 --- a/DSC/Tests/Acceptance/TestMofFiles.Tests.ps1 +++ /dev/null @@ -1,64 +0,0 @@ -$here = $PSScriptRoot -if ($global:Filter -and $global:Filter.ToString() -and -not $Filter.ToString()) -{ - $Filter = $global:Filter -} - -$datumDefinitionFile = Join-Path $here ..\..\DscConfigData\Datum.yml -$nodeDefinitions = Get-ChildItem $here\..\..\DscConfigData\AllNodes -Recurse -Include *.yml -$environments = (Get-ChildItem $here\..\..\DscConfigData\AllNodes -Directory).BaseName -$roleDefinitions = Get-ChildItem $here\..\..\DscConfigData\Roles -Recurse -Include *.yml -$datum = New-DatumStructure -DefinitionFile $datumDefinitionFile -$configurationData = Get-FilteredConfigurationData -Filter $Filter -Environment $environment -Datum $datum -CurrentJobNumber $currentJobNumber -TotalJobCount $totalJobCount - -$nodeNames = [System.Collections.ArrayList]::new() - -Describe 'Pull Server Deployment' -Tag BuildAcceptance, PullServer { - - $environmentNodes = $configurationData.AllNodes | Where-Object Environment -eq $env:RELEASE_ENVIRONMENTNAME - - foreach ($node in $environmentNodes) { - It "MOF file for node $($node.NodeName) was deployed to $($env:DscConfiguration)" { - - Get-ChildItem -Path $env:DscConfiguration -Filter "$($node.NodeName).mof" | Select-String -Pattern ">>$($env:BHBuildNumber)<<" | Should -Not -BeNullOrEmpty - - } - } - -} - -Describe 'MOF Files' -Tag BuildAcceptance { - BeforeAll { - $mofFiles = Get-ChildItem -Path "$buildOutput\MOF" -Filter *.mof -ErrorAction SilentlyContinue - $metaMofFiles = Get-ChildItem -Path "$buildOutput\MetaMOF" -Filter *.mof -ErrorAction SilentlyContinue - $nodes = $configurationData.AllNodes - } - - It 'All nodes have a MOF file' { - Write-Verbose "MOF File Count $($mofFiles.Count)" - Write-Verbose "Node Count $($nodes.Count)" - - $mofFiles.Count | Should -Be $nodes.Count - } - - foreach ($node in $nodes) { - It "Node '$($node.NodeName)' should have a MOF file" { - $mofFiles | Where-Object BaseName -eq $node.NodeName | Should -BeOfType System.IO.FileSystemInfo - } - } - - if ($metaMofFiles) { - It 'All nodes have a Meta MOF file' { - Write-Verbose "Meta MOF File Count $($metaMofFiles.Count)" - Write-Verbose "Node Count $($nodes.Count)" - - $metaMofFiles.Count | Should -BeIn $nodes.Count - } - - foreach ($node in $nodes) { - It "Node '$($node.NodeName)' should have a Meta MOF file" { - $metaMofFiles | Where-Object BaseName -eq "$($node.NodeName).meta" | Should -BeOfType System.IO.FileSystemInfo - } - } - } -} diff --git a/Exercises/Task2/Exercise1.md b/Exercises/Task2/Exercise1.md index e378b74a..c7c7fc71 100644 --- a/Exercises/Task2/Exercise1.md +++ b/Exercises/Task2/Exercise1.md @@ -125,7 +125,7 @@ After completing this task, you have a gone through the build process for all ar NetworkIpConfiguration: Interfaces: - - InterfaceAlias: Ethernet + - InterfaceAlias: DscWorkshop 0 IpAddress: 192.168.111.100 Prefix: 24 Gateway: 192.168.111.50 diff --git a/Exercises/Task2/Exercise2.md b/Exercises/Task2/Exercise2.md index 90462574..53ca225c 100644 --- a/Exercises/Task2/Exercise2.md +++ b/Exercises/Task2/Exercise2.md @@ -33,7 +33,7 @@ You are tasked with on-boarding a new node (DSCFile04) to your environment. The . NetworkIpConfiguration: Interfaces: - - InterfaceAlias: Ethernet + - InterfaceAlias: DscWorkshop 0 IpAddress: 192.168.111.112 . LcmConfig: diff --git a/Exercises/Task2/Exercise3.md b/Exercises/Task2/Exercise3.md index 1f45e77b..d742c1db 100644 --- a/Exercises/Task2/Exercise3.md +++ b/Exercises/Task2/Exercise3.md @@ -43,7 +43,7 @@ Create a new file in 'DSC\DscConfigData\Roles' named 'WsusServer.yml'. Paste the NetworkIpConfiguration: Interfaces: - - InterfaceAlias: Ethernet + - InterfaceAlias: DscWorkshop 0 IpAddress: 192.168.111.113 Prefix: 24 Gateway: 192.168.111.50 diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 00000000..8a364cd4 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,27 @@ +mode: ContinuousDelivery +next-version: 0.0.1 +major-version-bump-message: '(breaking\schange|breaking|major)\b' +minor-version-bump-message: '(adds?|features?|minor)\b' +patch-version-bump-message: '\s?(fix|patch)' +no-bump-message: '\+semver:\s?(none|skip)' +assembly-informational-format: '{NuGetVersionV2}+Sha.{Sha}.Date.{CommitDate}' +branches: + master: + tag: preview + regex: ^main$ + pull-request: + tag: PR + feature: + tag: useBranchName + increment: Minor + regex: f(eature(s)?)?[\/-] + source-branches: ['master'] + hotfix: + tag: fix + increment: Patch + regex: (hot)?fix(es)?[\/-] + source-branches: ['master'] + +ignore: + sha: [] +merge-message-formats: {} diff --git a/Lab/20 Lab Customizations.ps1 b/Lab/20 Lab Customizations.ps1 index 31529d35..9751b497 100644 --- a/Lab/20 Lab Customizations.ps1 +++ b/Lab/20 Lab Customizations.ps1 @@ -16,14 +16,8 @@ Write-Host 'Starting remaining VMs...' Start-LabVM -RoleName FileServer, WebServer, HyperV -Wait Write-Host 'Restarted all machines' -$psdependFiles = 'PSDepend.Build.psd1', 'PSDepend.DscResources.psd1' -$requiredModules = @{} - -foreach ($psdependFile in $psdependFiles) { - $psdependFileData = Import-PowerShellDataFile -Path "$here\..\DSC\$psdependFile" - $psdependFileData.Remove('PSDependOptions') - $requiredModules = $requiredModules + $psdependFileData -} +$requiredModules = Import-PowerShellDataFile -Path "$here\..\RequiredModules.psd1" +$requiredModules.Remove('PSDependOptions') #Adding modules that are not defined in the PSDepend files but required in the lab $requiredModules.NTFSSecurity = 'latest' @@ -37,13 +31,15 @@ $requiredChocolateyPackages = @{ vscode = '1.61.2' wireshark = '3.4.9' winpcap = '4.1.3.20161116' + 'gitversion.portable' = '5.7.0' + firefox = '94.0.1' } $vsCodeDownloadUrl = 'https://go.microsoft.com/fwlink/?Linkid=852157' -$gitDownloadUrl = 'https://github.com/git-for-windows/git/releases/download/v2.30.2.windows.1/Git-2.30.2-64-bit.exe' -$vscodePowerShellExtensionDownloadUrl = 'https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-vscode/vsextensions/PowerShell-Preview/2021.2.1/vspackage' +$gitDownloadUrl = 'https://github.com/git-for-windows/git/releases/download/v2.34.1.windows.1/Git-2.34.1-64-bit.exe' +$vscodePowerShellExtensionDownloadUrl = 'https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-vscode/vsextensions/PowerShell/2021.10.2/vspackage' $chromeDownloadUrl = 'https://dl.google.com/tag/s/appguid%3D%7B8A69D345-D564-463C-AFF1-A69D9E530F96%7D%26iid%3D%7BC9D94BD4-6037-E88E-2D5A-F6B7D7F8F4CF%7D%26lang%3Den%26browser%3D5%26usagestats%3D0%26appname%3DGoogle%2520Chrome%26needsadmin%3Dprefers%26ap%3Dx64-stable-statsdef_1%26installdataindex%3Dempty/chrome/install/ChromeStandaloneSetup64.exe' -$notepadPlusPlusDownloadUrl = 'https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v7.9.3/npp.7.9.3.Installer.exe' +$notepadPlusPlusDownloadUrl = 'https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v8.1.9.3/npp.8.1.9.3.Installer.exe' #------------------------------------------------------------------------------------------------------------------------------------- @@ -123,14 +119,14 @@ Invoke-LabCommand -Activity 'Setup Web Site' -ComputerName (Get-LabVM -Role WebS New-Item -ItemType Directory -Path C:\PSConfSite Expand-Archive -Path C:\LabSite.zip -DestinationPath C:\PSConfSite -Force - + $pool = New-WebAppPool -Name PSConfSite - $pool.processModel.identityType = 3 - $pool.processModel.userName = $deployUserName - $pool.processModel.password = $deployUserPassword + $pool.processModel.identityType = 3 + $pool.processModel.userName = $deployUserName + $pool.processModel.password = $deployUserPassword $pool | Set-Item - New-Website -name "PSConfSite" -PhysicalPath C:\PsConfSite -ApplicationPool "PSConfSite" + New-Website -name "PSConfSite" -PhysicalPath C:\PsConfSite -ApplicationPool "PSConfSite" } -Variable (Get-Variable deployUserName, deployUserPassword) # File server @@ -180,15 +176,15 @@ Invoke-LabCommand -ActivityName 'Create link on AzureDevOps desktop' -ComputerNa $shortcut = $shell.CreateShortcut("$desktopPath\CommonTasks Project.url") $shortcut.TargetPath = "https://$($devOpsServer):$($originalPort)/AutomatedLab/CommonTasks" $shortcut.Save() - + $shortcut = $shell.CreateShortcut("$desktopPath\PowerShell Feed.url") $shortcut.TargetPath = "https://$($devOpsServer):$($originalPort)/AutomatedLab/_packaging?_a=feed&feed=$($powerShellFeed.name)" $shortcut.Save() - + $shortcut = $shell.CreateShortcut("$desktopPath\Chocolatey Feed.url") $shortcut.TargetPath = "https://$($devOpsServer):$($originalPort)/AutomatedLab/_packaging?_a=feed&feed=$($chocolateyFeed.name)" $shortcut.Save() - + $shortcut = $shell.CreateShortcut("$desktopPath\SQL RS.url") $shortcut.TargetPath = "http://$sqlServer/Reports/browse/" $shortcut.Save() @@ -200,21 +196,21 @@ Invoke-LabCommand -ActivityName 'Create link on AzureDevOps desktop' -ComputerNa #in server 2019 there seems to be an issue with dynamic DNS registration, doing this manually foreach ($domain in (Get-Lab).Domains) { - $vms = Get-LabVM -All -IncludeLinux | Where-Object { + $vms = Get-LabVM -All -IncludeLinux | Where-Object { $_.DomainName -eq $domain.Name -and $_.OperatingSystem -like '*2019*' -or $_.OperatingSystem -like '*CentOS*' } - + $dc = Get-LabVM -Role ADDS | Where-Object DomainName -eq $domain.Name | Select-Object -First 1 - + Invoke-LabCommand -ActivityName 'Registering DNS records' -ScriptBlock { foreach ($vm in $vms) { if (-not (Get-DnsServerResourceRecord -Name $vm.Name -ZoneName $vm.DomainName -ErrorAction SilentlyContinue)) { "Running 'Add-DnsServerResourceRecord -ZoneName $($vm.DomainName) -IPv4Address $($vm.IpV4Address) -Name $($vm.Name) -A'" Add-DnsServerResourceRecord -ZoneName $vm.DomainName -IPv4Address $vm.IpV4Address -Name $vm.Name -A } - } + } } -ComputerName $dc -Variable (Get-Variable -Name vms) -PassThru } @@ -235,13 +231,13 @@ Invoke-LabCommand -ActivityName 'Get tested nuget.exe and register Azure DevOps } -Variable (Get-Variable -Name powerShellFeed) Invoke-LabCommand -ActivityName 'Install Chocolatey to all lab VMs' -ScriptBlock { - + if (([Net.ServicePointManager]::SecurityProtocol -band 'Tls12') -ne 'Tls12') { [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12 } - + Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) - + if (-not (Find-PackageProvider NuGet -ErrorAction SilentlyContinue)) { Install-PackageProvider -Name NuGet } @@ -250,7 +246,7 @@ Invoke-LabCommand -ActivityName 'Install Chocolatey to all lab VMs' -ScriptBlock if (-not (Get-PackageSource -Name $chocolateyFeed.name -ErrorAction SilentlyContinue)) { Register-PackageSource -Name $chocolateyFeed.name -ProviderName NuGet -Location $chocolateyFeed.NugetV2Url -Trusted } - + choco source add -n=Software -s $chocolateyFeed.NugetV2Url } -ComputerName (Get-LabVM) -Variable (Get-Variable -Name chocolateyFeed) @@ -266,11 +262,11 @@ Write-Host 'Restarting all other machines' Restart-LabVM -ComputerName (Get-LabVM -Role WebServer, FileServer, DSCPullServer, AzDevOps, SQLServer, HyperV) -Wait Invoke-LabCommand -ActivityName 'Add Chocolatey internal source' -ScriptBlock { - + if (([Net.ServicePointManager]::SecurityProtocol -band 'Tls12') -ne 'Tls12') { [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12 } - + choco source add -n=Software -s $chocolateyFeed.NugetV2Url } -ComputerName (Get-LabVM) -Variable (Get-Variable -Name chocolateyFeed) @@ -278,7 +274,7 @@ Invoke-LabCommand -ActivityName 'Add Chocolatey internal source' -ScriptBlock { Invoke-LabCommand -ActivityName 'Downloading required modules from PSGallery' -ComputerName $devOpsServer -ScriptBlock { Write-Host "Installing $($requiredModules.Count) modules on $(hostname.exe) for pushing them to the lab" - + foreach ($requiredModule in $requiredModules.GetEnumerator()) { $installModuleParams = @{ Name = $requiredModule.Key @@ -314,12 +310,28 @@ Invoke-LabCommand -ActivityName 'Publishing required modules to internal reposit $version = "$version-$($module.PrivateData.PSData.Prerelease)" } Write-Host "`t'$($module.Name) - $version'" - if (-not (Find-Module -Name $module.Name -Repository PowerShell -ErrorAction SilentlyContinue)) { - Publish-Module -Name $module.Name -RequiredVersion $version -Repository PowerShell -NuGetApiKey $nuGetApiKey -AllowPrerelease -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + if (-not (Find-Module -Name $module.Name -Repository PowerShell -AllowPrerelease -ErrorAction SilentlyContinue)) { + $param = @{ + Name = $module.Name + RequiredVersion = $version + Repository = 'PowerShell' + NuGetApiKey = $nuGetApiKey + AllowPrerelease = $true + Force = $true + ErrorAction = 'SilentlyContinue' + WarningAction = 'SilentlyContinue' + } + #Removing ErrorAction and WarningAction to see errors in the last publish loop. + if ($loop -eq $loopCount) { + $param.Remove('ErrorAction') + $param.Remove('WarningAction') + } + + Publish-Module @param } } } - + $modulesToUninstall = $requiredModules.GetEnumerator() | Where-Object Key -NotIn PowerShellGet, PackageManagement Write-Host "Uninstalling $($requiredModules.Count) modules" foreach ($module in $modulesToUninstall) { @@ -333,11 +345,11 @@ Invoke-LabCommand -ActivityName 'Publishing required modules to internal reposit } -Variable (Get-Variable -Name requiredModules, nuGetApiKey) Invoke-LabCommand -ActivityName 'Publishing required Chocolatey packages to internal repository' -ScriptBlock { - + if (([Net.ServicePointManager]::SecurityProtocol -band 'Tls12') -ne 'Tls12') { [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12 } - + Import-PackageProvider -Name NuGet $publicFeedUri = 'https://chocolatey.org/api/v2/' @@ -360,7 +372,7 @@ Invoke-LabCommand -ActivityName 'Publishing required Chocolatey packages to inte } dir -Path $tempFolder | ForEach-Object { - + Write-Host "Publishing package '$($_.FullName)'" choco push $_.FullName -s $chocolateyFeed.NugetV2Url --api-key $chocolateyFeed.NugetApiKey @@ -380,27 +392,27 @@ Invoke-LabCommand -ActivityName 'Create Artifacts Share' -ComputerName $devOpsSe Install-Module -Name NTFSSecurity -Repository PowerShell mkdir -Path $artifactsSharePath - + New-SmbShare -Name $artifactsShareName -Path $artifactsSharePath -FullAccess Everyone Add-NTFSAccess -Path $artifactsSharePath -Account Everyone -AccessRights FullControl } Invoke-LabCommand -ActivityName 'Create Share on Pull Server' -ComputerName $pullServer -ScriptBlock { Install-Module -Name NTFSSecurity -Repository PowerShell - + $dscModulesPath = 'C:\Program Files\WindowsPowerShell\DscService\Modules' $dscConfigurationPath = 'C:\Program Files\WindowsPowerShell\DscService\Configuration' New-SmbShare -Name DscModules -Path $dscModulesPath -FullAccess Everyone Add-NTFSAccess -Path $dscModulesPath -Account Everyone -AccessRights FullControl - + New-SmbShare -Name DscConfiguration -Path $dscConfigurationPath -FullAccess Everyone Add-NTFSAccess -Path $dscConfigurationPath -Account Everyone -AccessRights FullControl } Invoke-LabCommand -ActivityName 'Setting the worker service account to local system to be able to write to deployment path' -ComputerName $buildWorkers -ScriptBlock { $services = Get-CimInstance -ClassName Win32_Service -Filter 'Name like "vsts%"' - foreach ($service in $services) { + foreach ($service in $services) { $service | Invoke-CimMethod -MethodName Change -Arguments @{ StartName = 'LocalSystem' } | Out-Null } } diff --git a/Lab/31 New Release Pipeline CommonTasks.ps1 b/Lab/31 New Release Pipeline CommonTasks.ps1 index 1cde9bd7..cfe04f38 100644 --- a/Lab/31 New Release Pipeline CommonTasks.ps1 +++ b/Lab/31 New Release Pipeline CommonTasks.ps1 @@ -1,18 +1,32 @@ -if (-not (Get-Lab -ErrorAction SilentlyContinue).Name -eq 'DscWorkshop') { +if (-not (Get-Lab -ErrorAction SilentlyContinue).Name -eq 'DscWorkshop') +{ Import-Lab -Name DscWorkshop -NoValidation -ErrorAction Stop } $projectGitUrl = 'https://github.com/raandree/CommonTasks' $projectName = $projectGitUrl.Substring($projectGitUrl.LastIndexOf('/') + 1) $collectionName = 'AutomatedLab' +$gitVersion = @{ + Publisher = 'GitTools' + Extension = 'GitVersion' + Version = '5.0.1.3' + VsixPath = 'C:\gittools.gitversion-5.0.1.3.vsix' +} $lab = Get-Lab $devOpsServer = Get-LabVM -Role AzDevOps $devOpsWorker = Get-LabVM -Role HyperV -$devOpsHostName = if ($lab.DefaultVirtualizationEngine -eq 'Azure') { $devOpsServer.AzureConnectionInfo.DnsName } else { $devOpsServer.FQDN } +$devOpsHostName = if ($lab.DefaultVirtualizationEngine -eq 'Azure') +{ + $devOpsServer.AzureConnectionInfo.DnsName +} +else +{ + $devOpsServer.FQDN +} $nugetServer = Get-LabVM -Role AzDevOps $nugetFeed = Get-LabTfsFeed -ComputerName $nugetServer -FeedName PowerShell -$devOpsRole = $devOpsServer.Roles | Where-Object Name -eq AzDevOps +$devOpsRole = $devOpsServer.Roles | Where-Object Name -EQ AzDevOps $devOpsCred = $devOpsServer.GetCredential($lab) $devOpsPort = $originalPort = 8080 if ($devOpsRole.Properties.ContainsKey('Port')) @@ -28,6 +42,25 @@ if ($lab.DefaultVirtualizationEngine -eq 'Azure') # You will see two remotes, Origin (Our code on GitHub) and Azure DevOps (Our code pushed to your lab) Write-ScreenInfo 'Creating Azure DevOps project and cloning from GitHub...' -NoNewLine +Copy-LabFileItem -Path $PSScriptRoot\LabData\gittools.gitversion-5.0.1.3.vsix -ComputerName $devOpsServer +Invoke-LabCommand -ActivityName "Uploading 'GitVersion' extension" -ComputerName $devOpsServer -ScriptBlock { + + $param = @{ + Uri = "https://$($devOpsHostName):$devOpsPort/_apis/gallery/extensions?api-version=3.0-preview.1" + Credential = $devOpsCred + Body = '{{"extensionManifest": "{0}"}}' -f ([Convert]::ToBase64String([IO.File]::ReadAllBytes($gitVersion.VsixPath))) + Method = 'POST' + ContentType = 'application/json' + } + if ($PSVersionTable.PSEdition -eq 'Core') + { + $param.Add('SkipCertificateCheck', $true) + } + + $null = (Invoke-RestMethod @param) + +} -Variable (Get-Variable -Name devOpsCred, devOpsHostName, devOpsPort, collectionName, gitVersion) + New-LabReleasePipeline -ProjectName $projectName -SourceRepository $projectGitUrl -CodeUploadMethod FileCopy Invoke-LabCommand -ActivityName 'Bootstrap NuGet.exe' -ComputerName $devOpsServer, $devOpsWorker -ScriptBlock { @@ -35,27 +68,31 @@ Invoke-LabCommand -ActivityName 'Bootstrap NuGet.exe' -ComputerName $devOpsServe $nugetPathAllUsers = "$([System.Environment]::GetFolderPath('CommonApplicationData'))\Microsoft\Windows\PowerShell\PowerShellGet\NuGet.exe" $nugetPathCurrentUser = "$([System.Environment]::GetFolderPath('LocalApplicationData'))\Microsoft\Windows\PowerShell\PowerShellGet\NuGet.exe" - if (-not (Test-Path -Path $nugetPath)) { + if (-not (Test-Path -Path $nugetPath)) + { mkdir -Path $nugetPath } - $hasNuget = if (Test-Path -Path $nugetPathAllUsers) { - + $hasNuget = if (Test-Path -Path $nugetPathAllUsers) + { $nugetExe = Get-Item -Path $nugetPathAllUsers Write-Host "'nuget.exe' exist in '$nugetPathAllUsers' with version '$($nugetExe.VersionInfo.FileVersionRaw)'" - if ($nugetExe.VersionInfo.FileVersionRaw -gt '5.11') { + if ($nugetExe.VersionInfo.FileVersionRaw -gt '5.11') + { $true } } - if (-not $hasNuget) { - if (Test-Path -Path $nugetPathCurrentUser) { - + if (-not $hasNuget) + { + if (Test-Path -Path $nugetPathCurrentUser) + { $nugetExe = Get-Item -Path $nugetPathCurrentUser Write-Host "'nuget.exe' exist in '$nugetPathCurrentUser' with version '$($nugetExe.VersionInfo.FileVersionRaw)'" - if ($nugetExe.VersionInfo.FileVersionRaw -gt '5.11') { + if ($nugetExe.VersionInfo.FileVersionRaw -gt '5.11') + { $hasNuget = $true } } @@ -67,11 +104,13 @@ Invoke-LabCommand -ActivityName 'Bootstrap NuGet.exe' -ComputerName $devOpsServe Invoke-WebRequest -Uri 'https://aka.ms/psget-nugetexe' -OutFile $nugetPathCurrentUser -ErrorAction Stop - if (Test-Path -Path $nugetPathCurrentUser) { + if (Test-Path -Path $nugetPathCurrentUser) + { $nugetExe = Get-Item -Path $nugetPathCurrentUser -ErrorAction SilentlyContinue Write-Host "'nuget.exe' exist in '$nugetPathCurrentUser' with version '$($nugetExe.VersionInfo.FileVersionRaw)'" - if ($nugetExe.VersionInfo.FileVersionRaw -lt '5.11') { + if ($nugetExe.VersionInfo.FileVersionRaw -lt '5.11') + { Write-Host "'nuget.exe' has the version '$($nugetExe.VersionInfo.FileVersionRaw)' and needs to be updated." Invoke-WebRequest -Uri 'https://aka.ms/psget-nugetexe' -OutFile $nugetPathCurrentUser -ErrorAction Stop } @@ -88,38 +127,18 @@ Invoke-LabCommand -ActivityName 'Bootstrap NuGet.exe' -ComputerName $devOpsServe } Copy-Item -Path $nugetExe -Destination $nugetPath - + [System.Environment]::SetEnvironmentVariable('Path', $env:Path + ';C:\NuGet', 'Machine') } -Copy-LabFileItem -Path $PSScriptRoot\LabData\gittools.gitversion-5.0.1.3.vsix -ComputerName $devOpsServer - -Invoke-LabCommand -ActivityName "Upload and install 'GitVersion' extension" -ComputerName $devOpsServer -ScriptBlock { - $publisher = "GitTools" - $extension = "GitVersion" - $version = '5.0.1.3' - $vsix = 'C:\gittools.gitversion-5.0.1.3.vsix' - - $param = @{ - Uri = "https://$($devOpsHostName):$devOpsPort/_apis/gallery/extensions?api-version=3.0-preview.1" - Credential = $devOpsCred - Body = '{{"extensionManifest": "{0}"}}' -f ([Convert]::ToBase64String([IO.File]::ReadAllBytes($vsix))) - Method = 'POST' - ContentType = 'application/json' - } - if ($PSVersionTable.PSEdition -eq 'Core') - { - $param.Add('SkipCertificateCheck', $true) - } +Invoke-LabCommand -ActivityName "Installing 'GitVersion' extension" -ComputerName $devOpsServer -ScriptBlock { - $result = (Invoke-RestMethod @param) - Start-Sleep -Seconds 10 - $param = @{ - Uri = "https://$($devOpsHostName):$devOpsPort/$collectionName/_apis/extensionmanagement/installedextensionsbyname/$publisher/$extension/$($version)?api-version=5.0-preview.1" - Credential = $devOpsCred - Method = 'POST' + $param = @{ + Uri = "https://$($devOpsHostName):$devOpsPort/$collectionName/_apis/extensionmanagement/installedextensionsbyname/$($gitVersion.Publisher)/$($gitVersion.Extension)/$($gitVersion.Version)?api-version=5.0-preview.1" + Credential = $devOpsCred + Method = 'POST' ContentType = 'application/json' } if ($PSVersionTable.PSEdition -eq 'Core') @@ -127,14 +146,14 @@ Invoke-LabCommand -ActivityName "Upload and install 'GitVersion' extension" -Com $param.Add('SkipCertificateCheck', $true) } - $result = (Invoke-RestMethod @param) + $null = (Invoke-RestMethod @param) -} -Variable (Get-Variable -Name devOpsCred, devOpsHostName, devOpsPort, collectionName) +} -Variable (Get-Variable -Name devOpsCred, devOpsHostName, devOpsPort, collectionName, gitVersion) Invoke-LabCommand -ActivityName 'Set Repository and create Build Pipeline' -ScriptBlock { Set-Location -Path C:\Git\CommonTasks - git checkout dev *>$null + git checkout main *>$null Remove-Item -Path '.\azure-pipelines.yml' (Get-Content -Path '.\azure-pipelines On-Prem.yml' -Raw) -replace 'RepositoryUri_WillBeChanged', $nugetFeed.NugetV2Url | Set-Content -Path .\azure-pipelines.yml (Get-Content -Path .\Resolve-Dependency.psd1 -Raw) -replace 'PSGallery', 'PowerShell' | Set-Content -Path .\Resolve-Dependency.psd1 @@ -145,7 +164,7 @@ Invoke-LabCommand -ActivityName 'Set Repository and create Build Pipeline' -Scri } -ComputerName $devOpsServer -Variable (Get-Variable -Name nugetFeed) -Write-Host 'Restarting Azure DevOps Server and worker machine...' -NoNewLine +Write-Host 'Restarting Azure DevOps Server and worker machine...' -NoNewline Restart-LabVM -ComputerName (Get-LabVM -Role AzDevOps, HyperV) -Wait -NoDisplay Write-Host 'done' diff --git a/Lab/32 New Release Pipeline DscWorkshop.ps1 b/Lab/32 New Release Pipeline DscWorkshop.ps1 index 654704ee..da29cfa2 100644 --- a/Lab/32 New Release Pipeline DscWorkshop.ps1 +++ b/Lab/32 New Release Pipeline DscWorkshop.ps1 @@ -29,534 +29,23 @@ if ($lab.DefaultVirtualizationEngine -eq 'Azure') { Write-ScreenInfo 'Creating Azure DevOps project and cloning from GitHub...' -NoNewLine New-LabReleasePipeline -ProjectName $projectName -SourceRepository $projectGitUrl -CodeUploadMethod FileCopy -$tfsAgentQueue = Get-TfsAgentQueue -InstanceName $devOpsHostName -Port $devOpsPort -Credential $devOpsCred -ProjectName $projectName -CollectionName $collectionName -QueueName Default -UseSsl -SkipCertificateCheck - -#region Release Definitions -$releaseSteps = @( - @{ - taskId = '5bfb729a-a7c8-4a78-a7c3-8d717bb7c13c' - version = '2.*' - name = 'Copy Files to: Artifacte Share' - enabled = $true - condition = 'succeeded()' - inputs = @{ - SourceFolder = '$(System.DefaultWorkingDirectory)/$(Release.PrimaryArtifactSourceAlias)' - Contents = '**' - TargetFolder = '\\{0}\Artifacts\$(Build.DefinitionName)\$(Build.BuildNumber)\$(Build.Repository.Name)' -f $devOpsServer.FQDN - } - } - @{ - enabled = $true - name = 'Register PowerShell Gallery' - taskId = 'e213ff0f-5d5c-4791-802d-52ea3e7be1f1' - version = '2.*' - inputs = @{ - targetType = 'inline' - script = @' -#always make sure the local PowerShell Gallery is registered correctly -$uri = '$(RepositoryUri)' -$name = 'PowerShell' -$r = Get-PSRepository -Name $name -ErrorAction SilentlyContinue -if (-not $r -or $r.SourceLocation -ne $uri -or $r.PublishLocation -ne $uri) { - Write-Host "The Source or PublishLocation of the repository '$name' is not correct or the repository is not registered" - Unregister-PSRepository -Name $name -ErrorAction SilentlyContinue - Register-PSRepository -Name $name -SourceLocation $uri -PublishLocation $uri -InstallationPolicy Trusted - Get-PSRepository -} -'@ - } - } - @{ - enabled = $true - name = "Print Environment Variables" - taskid = 'e213ff0f-5d5c-4791-802d-52ea3e7be1f1' - version = '2.*' - inputs = @{ - targetType = "inline" - script = 'dir -Path env:' - } - } - @{ - enabled = $true - name = "Execute Build.ps1 for Deployment" - taskId = 'e213ff0f-5d5c-4791-802d-52ea3e7be1f1' - version = '2.*' - inputs = @{ - targetType = 'inline' - script = @' -Write-Host $(System.DefaultWorkingDirectory) -Set-Location -Path '$(System.DefaultWorkingDirectory)\$(Release.PrimaryArtifactSourceAlias)\SourcesDirectory\DSC' -.\Build.ps1 -Tasks Init, SetPsModulePath, Deploy, TestBuildAcceptance -Repository PowerShell -'@ - } - } - @{ - enabled = $true - name = 'Publish Build Acceptance Test Results' - condition = 'always()' - taskid = '0b0f01ed-7dde-43ff-9cbb-e48954daf9b1' - version = '*' - inputs = @{ - testRunner = 'NUnit' - testResultsFiles = '**/BuildAcceptanceTestResults.xml' - searchFolder = '$(System.DefaultWorkingDirectory)' - } - } - @{ - taskId = '3b5693d4-5777-4fee-862a-bd2b7a374c68' - version = '3.*' - name = 'Create Dev Test Machines' - enabled = $false - inputs = @{ - Machines = $hypervHost.FQDN - UserName = '$(InstallUserName)' - UserPassword = '$(InstallUserPassword)' - ScriptType = 'Inline' - InlineScript = @' -[System.Environment]::SetEnvironmentVariable('AUTOMATEDLAB_TELEMETRY_OPTOUT', '0') -Import-Module -Name AutomatedLab -$dc = Get-ADDomainController - -$netAdapter = Get-NetAdapter -Name 'vEthernet (AlExternal)' -ErrorAction SilentlyContinue -if (-not $netAdapter) -{ - $netAdapter = Get-NetAdapter -Name Ethernet -ErrorAction SilentlyContinue -} - -$ip = $netAdapter | Get-NetIPAddress -AddressFamily IPv4 -$network = [AutomatedLab.IPNetwork]"$($ip.IPAddress)/$($ip.PrefixLength)" - -#-------------------------------- - -New-LabDefinition -Name Lab1 -DefaultVirtualizationEngine HyperV - -$os = Get-LabAvailableOperatingSystem | Where-Object { $_.OperatingSystemName -like '*Datacenter*' -and $_.OperatingSystemName -like '*2019*' -and $_.OperatingSystemName -like '*Desktop*' } -$PSDefaultParameterValues = @{ - 'Add-LabMachineDefinition:OperatingSystem'= $os - 'Add-LabMachineDefinition:Memory'= 1GB - 'Add-LabMachineDefinition:DomainName'= $dc.Domain - 'Add-LabMachineDefinition:DnsServer1' = $dc.IPv4Address - 'Add-LabMachineDefinition:Gateway' = (Get-NetIPConfiguration).IPv4DefaultGateway.NextHop -} - -Add-LabVirtualNetworkDefinition -Name AlExternal -AddressSpace "$($ip.IPAddress)/$($ip.PrefixLength)" -HyperVProperties @{ SwitchType = 'External'; AdapterName = 'Ethernet' } - -$param = @{ - Name = $dc.Name - Roles = 'RootDC' - IpAddress = $dc.IPv4Address - SkipDeployment = $true -} -Add-LabMachineDefinition @param - -$param = @{ - Name = 'DSCFile01' - Roles = 'FileServer' - IpAddress = [AutomatedLab.IPNetwork]::ListIPAddress($network)[100] -} -Add-LabMachineDefinition @param - -$param = @{ - Name = 'DSCWeb01' - Roles = 'WebServer' - IpAddress = [AutomatedLab.IPNetwork]::ListIPAddress($network)[101] -} -Add-LabMachineDefinition @param - -Install-Lab - -Show-LabDeploymentSummary -Detailed -'@ - CommunicationProtocol = 'Http' - AuthenticationMechanism = 'Credssp' - NewPsSessionOptionArguments = '-SkipCACheck -IdleTimeout 7200000 -OperationTimeout 0 -OutputBufferingMode Block' - } - } - @{ - enabled = $false - name = 'Wait' - taskId = 'e213ff0f-5d5c-4791-802d-52ea3e7be1f1' - version = '2.*' - inputs = @{ - targetType = 'inline' - script = @' -Start-Sleep -Seconds 30 -'@ - } - } - @{ - taskId = '3b5693d4-5777-4fee-862a-bd2b7a374c68' - version = '3.*' - name = 'Remove Dev Test Machines' - enabled = $false - inputs = @{ - Machines = $hypervHost.FQDN - UserName = '$(InstallUserName)' - UserPassword = '$(InstallUserPassword)' - ScriptType = 'Inline' - InlineScript = @' - [System.Environment]::SetEnvironmentVariable('AUTOMATEDLAB_TELEMETRY_OPTOUT', '0') - Import-Module -Name AutomatedLab - Remove-Lab -Name Lab1 -Confirm:$false -'@ - CommunicationProtocol = 'Http' - AuthenticationMechanism = 'Credssp' - NewPsSessionOptionArguments = '-SkipCACheck -IdleTimeout 7200000 -OperationTimeout 0 -OutputBufferingMode Block' - } - } -) - -$releaseEnvironments = @( - @{ - id = 3 - name = 'Dev' - rank = 1 - owner = @{ - displayName = 'Install' - id = '196672db-49dd-4968-8c52-a94e43186ffd' - uniqueName = 'Install' - } - variables = @{ - RepositoryUri = @{ value = $nugetFeed.NugetV2Url } - InstallUserName = @{ value = $devOpsCred.UserName } - InstallUserPassword = @{ value = $devOpsCred.GetNetworkCredential().Password } - DscConfiguration = @{ value = "\\$($pullServer.FQDN)\DscConfiguration" } - DscModules = @{ value = "\\$($pullServer.FQDN)\DscModules" } - } - preDeployApprovals = @{ - approvals = @( - @{ - rank = 1 - isAutomated = $true - isNotificationOn = $false - id = 7 - } - ) - } - deployStep = @{ id = 10 } - postDeployApprovals = @{ - approvals = @( - @{ - rank = 1 - isAutomated = $true - isNotificationOn = $false - id = 11 - } - ) - } - deployPhases = @( - @{ - deploymentInput = @{ - parallelExecution = @{ parallelExecutionType = 'none' } - skipArtifactsDownload = $false - artifactsDownloadInput = @{ downloadInputs = $() } - queueId = $tfsAgentQueue.id - demands = @() - enableAccessToken = $false - timeoutInMinutes = 0 - jobCancelTimeoutInMinutes = 1 - condition = 'succeeded()' - overrideInputs = @{ } - } - rank = 1 - phaseType = 1 - name = 'Run on agent' - workflowTasks = $releaseSteps - } - ) - environmentOptions = @{ - emailNotificationType = 'OnlyOnFailure' - emailRecipients = 'release.environment.owner;release.creator' - skipArtifactsDownload = $false - timeoutInMinutes = 0 - enableAccessToken = $false - publishDeploymentStatus = $true - badgeEnabled = $false - autoLinkWorkItems = $false - } - demands = @() - conditions = @( - @{ - name = 'ReleaseStarted' - conditionType = 1 - } - ) - executionPolicy = @{ - concurrencyCount = 0 - queueDepthCount = 0 - } - schedules = @() - retentionPolicy = @{ - daysToKeep = 30 - releasesToKeep = 3 - retainBuild = $true - } - processParameters = @{ } - properties = @{ } - preDeploymentGates = @{ - id = 0 - gatesOptions = $null - gates = @() - } - postDeploymentGates = @{ - id = 0 - gatesOptions = $null - gates = @() - } - } - @{ - id = 4 - name = 'Test' - rank = 2 - owner = @{ - displayName = 'Install' - id = '196672db-49dd-4968-8c52-a94e43186ffd' - uniqueName = 'Install' - } - variables = @{ - RepositoryUri = @{ value = $nugetFeed.NugetV2Url } - InstallUserName = @{ value = $devOpsCred.UserName } - InstallUserPassword = @{ value = $devOpsCred.GetNetworkCredential().Password } - DscConfiguration = @{ value = "\\$($pullServer.FQDN)\DscConfiguration" } - DscModules = @{ value = "\\$($pullServer.FQDN)\DscModules" } - } - preDeployApprovals = @{ - approvals = @( - @{ - rank = 1 - isAutomated = $true - isNotificationOn = $false - id = 7 - } - ) - } - deployStep = @{ id = 10 } - postDeployApprovals = @{ - approvals = @( - @{ - rank = 1 - isAutomated = $true - isNotificationOn = $false - id = 11 - } - ) - } - deployPhases = @( - @{ - deploymentInput = @{ - parallelExecution = @{ parallelExecutionType = 'none' } - skipArtifactsDownload = $false - artifactsDownloadInput = @{ downloadInputs = $() } - queueId = $tfsAgentQueue.id - demands = @() - enableAccessToken = $false - timeoutInMinutes = 0 - jobCancelTimeoutInMinutes = 1 - condition = 'succeeded()' - overrideInputs = @{ } - } - rank = 1 - phaseType = 1 - name = 'Run on agent' - workflowTasks = $releaseSteps | Select-Object -Skip 1 - } - ) - environmentOptions = @{ - emailNotificationType = 'OnlyOnFailure' - emailRecipients = 'release.environment.owner;release.creator' - skipArtifactsDownload = $false - timeoutInMinutes = 0 - enableAccessToken = $false - publishDeploymentStatus = $true - badgeEnabled = $false - autoLinkWorkItems = $false - } - demands = @() - conditions = @( - @{ - name = 'Dev' - conditionType = 2 - value = 4 - } - @{ - name = 'DscWorkshop CI' - conditionType = 4 - value = '{"sourceBranch":"master","tags":[],"useBuildDefinitionBranch":false}' - } - ) - executionPolicy = @{ - concurrencyCount = 0 - queueDepthCount = 0 - } - schedules = @() - retentionPolicy = @{ - daysToKeep = 30 - releasesToKeep = 3 - retainBuild = $true - } - processParameters = @{ } - properties = @{ } - preDeploymentGates = @{ - id = 0 - gatesOptions = $null - gates = @() - } - postDeploymentGates = @{ - id = 0 - gatesOptions = $null - gates = @() - } - } - @{ - id = 5 - name = "Prod" - rank = 3 - owner = @{ - displayName = 'Install' - id = '196672db-49dd-4968-8c52-a94e43186ffd' - uniqueName = 'Install' - } - variables = @{ - RepositoryUri = @{ value = $nugetFeed.NugetV2Url } - InstallUserName = @{ value = $devOpsCred.UserName } - InstallUserPassword = @{ value = $devOpsCred.GetNetworkCredential().Password } - DscConfiguration = @{ value = "\\$($pullServer.FQDN)\DscConfiguration" } - DscModules = @{ value = "\\$($pullServer.FQDN)\DscModules" } - } - preDeployApprovals = @{ - approvals = @( - @{ - rank = 1 - isAutomated = $false - isNotificationOn = $false - approver = @{ - displayName = 'Install' - id = '196672db-49dd-4968-8c52-a94e43186ffd' - uniqueName = $devOpsCred.UserName - } - } - ) - } - deployStep = @{ id = 10 } - postDeployApprovals = @{ - approvals = @( - @{ - rank = 1 - isAutomated = $true - isNotificationOn = $false - id = 11 - } - ) - } - deployPhases = @( - @{ - deploymentInput = @{ - parallelExecution = @{ parallelExecutionType = 'none' } - skipArtifactsDownload = $false - artifactsDownloadInput = @{ downloadInputs = $() } - queueId = $tfsAgentQueue.id - demands = @() - enableAccessToken = $false - timeoutInMinutes = 0 - jobCancelTimeoutInMinutes = 1 - condition = 'succeeded()' - overrideInputs = @{ } - } - rank = 1 - phaseType = 1 - name = 'Run on agent' - workflowTasks = $releaseSteps | Select-Object -Skip 1 - } - ) - environmentOptions = @{ - emailNotificationType = 'OnlyOnFailure' - emailRecipients = 'release.environment.owner;release.creator' - skipArtifactsDownload = $false - timeoutInMinutes = 0 - enableAccessToken = $false - publishDeploymentStatus = $true - badgeEnabled = $false - autoLinkWorkItems = $false - } - demands = @() - conditions = @( - @{ - name = 'Test' - conditionType = 2 - value = 4 - } - @{ - name = 'DscWorkshop CI' - conditionType = 4 - value = '{"sourceBranch":"master","tags":[],"useBuildDefinitionBranch":false}' - } - ) - executionPolicy = @{ - concurrencyCount = 0 - queueDepthCount = 0 - } - schedules = @() - retentionPolicy = @{ - daysToKeep = 30 - releasesToKeep = 3 - retainBuild = $true - } - processParameters = @{ } - properties = @{ } - preDeploymentGates = @{ - id = 0 - gatesOptions = $null - gates = @() - } - postDeploymentGates = @{ - id = 0 - gatesOptions = $null - gates = @() - } - } -) -#endregion Build and Release Definitions - -$repo = Get-TfsGitRepository -InstanceName $devOpsHostName -Port $devOpsPort -CollectionName $collectionName -ProjectName $projectName -Credential $devOpsCred -UseSsl -SkipCertificateCheck - -$param = @{ - Uri = "https://$($devOpsHostName):$devOpsPort/$collectionName/_apis/git/repositories/{$($repo.id)}/refs?api-version=4.1" - Credential = $devOpsCred -} -if ($PSVersionTable.PSEdition -eq 'Core') { - $param.Add('SkipCertificateCheck', $true) -} Invoke-LabCommand -ActivityName 'Set RepositoryUri and create Build Pipeline' -ScriptBlock { Set-Location -Path C:\Git\DscWorkshop - git checkout dev *>$null - $c = Get-Content '.\azure-pipelines On-Prem.yml' -Raw - $c = $c -replace ' RepositoryUri: ggggg', " RepositoryUri: $($nugetFeed.NugetV2Url)" - $c | Set-Content '.\azure-pipelines.yml' + git checkout main *>$null + Remove-Item -Path '.\azure-pipelines.yml' + (Get-Content -Path '.\azure-pipelines On-Prem.yml' -Raw) -replace 'RepositoryUri_WillBeChanged', $nugetFeed.NugetV2Url | Set-Content -Path .\azure-pipelines.yml + (Get-Content -Path .\Resolve-Dependency.psd1 -Raw) -replace 'PSGallery', 'PowerShell' | Set-Content -Path .\Resolve-Dependency.psd1 + (Get-Content -Path .\RequiredModules.psd1 -Raw) -replace 'PSGallery', 'PowerShell' | Set-Content -Path .\RequiredModules.psd1 git add . git commit -m 'Set RepositoryUri and create Build Pipeline' git push 2>$null } -ComputerName $devOpsServer -Variable (Get-Variable -Name nugetFeed) -Start-Sleep -Seconds 10 -$releaseParameters = @{ - ProjectName = $projectName - InstanceName = $devOpsHostName - Port = $devOpsPort - ReleaseName = "$($projectName) CD" - Environments = $releaseEnvironments - Credential = $devOpsCred - CollectionName = $collectionName - UseSsl = $true - SkipCertificateCheck = $true -} -New-TfsReleaseDefinition @releaseParameters - - Write-ScreenInfo done # in case you screw something up -Checkpoint-LabVM -All -SnapshotName AfterPipelines +#Checkpoint-LabVM -All -SnapshotName AfterPipelines Write-Host "3. - Creating Snapshot 'AfterPipelines'" -ForegroundColor Magenta diff --git a/Lab/LabData/Helpers.psm1 b/Lab/LabData/Helpers.psm1 index d0a63f2b..fa6833b6 100644 --- a/Lab/LabData/Helpers.psm1 +++ b/Lab/LabData/Helpers.psm1 @@ -7,7 +7,7 @@ function Set-LabDscLatestMetaMofs { $latestBuild = Get-LabLatestArtifactsPath - $path = Join-Path -Path $latestBuild -ChildPath 'Meta MOF' + $path = Join-Path -Path $latestBuild -ChildPath MetaMOF Set-DscLocalConfigurationManager -Path $path -Verbose -Force } @@ -16,7 +16,7 @@ function Update-LabDscNodes { $computers = Get-ADComputer -Filter { Name -like 'DSCWeb*' -or Name -like 'DSCFile*' } | Select-Object -ExpandProperty DNSHostName Update-DscConfiguration -ComputerName $computers -Verbose -Wait - + Start-DscConfiguration -UseExisting -Wait -Verbose -Force -ComputerName $computers } @@ -27,8 +27,7 @@ function Show-LabLatestArtifacts { function Get-LabLatestArtifactsPath { $latestBuild = dir -Path 'C:\Artifacts\DscWorkshop CI' | Sort-Object -Property { [int]$_.Name } -Descending | Select-Object -First 1 - $latestBuild = Join-Path $latestBuild.FullName -ChildPath DscWorkshop - $latestBuild + $latestBuild.FullName } function Initialize-DscLocalConfigurationManager { @@ -37,15 +36,15 @@ function Initialize-DscLocalConfigurationManager { if (-not (Test-Path -Path $lcmConfigPath)) { New-Item -Path $lcmConfigPath -ItemType Directory -Force | Out-Null } - + $lcmConfig = @' Configuration LocalConfigurationManagerConfiguration { LocalConfigurationManager { - + ConfigurationMode = 'ApplyOnly' - + } } '@ @@ -53,7 +52,7 @@ Configuration LocalConfigurationManagerConfiguration Invoke-Command -ScriptBlock ([scriptblock]::Create($lcmConfig)) -NoNewScope LocalConfigurationManagerConfiguration -OutputPath $lcmConfigPath | Out-Null - + Set-DscLocalConfigurationManager -Path $lcmConfigPath -Force Remove-Item -LiteralPath $lcmConfigPath -Recurse -Force -Confirm:$false } @@ -83,7 +82,7 @@ function Clear-LabDscNodes { $path = Join-Path (Get-LabLatestArtifactsPath) -ChildPath MOF $computerNames = dir -Path $path -Filter *.mof | Select-Object -ExpandProperty BaseName - Invoke-Command -ScriptBlock { + Invoke-Command -ScriptBlock { Remove-Item HKLM:\SOFTWARE\DscLcmController -Recurse -Force Remove-DscConfigurationDocument -Stage Current, Pending, Previous Remove-Item -Path C:\ProgramData\Dsc -Force -Recurse @@ -105,13 +104,13 @@ function Clear-LabBuildWorkers { function Test-LabDscConfiguration { $computers = Get-ADComputer -Filter { Name -like 'DSCWeb*' -or Name -like 'DSCFile*' } | Select-Object -ExpandProperty DNSHostName - + Test-DscConfiguration -ComputerName $computers -Detailed -Verbose } function Start-LabDscConfiguration { $computers = Get-ADComputer -Filter { Name -like 'DSCWeb*' -or Name -like 'DSCFile*' } | Select-Object -ExpandProperty DNSHostName - + Start-DscConfiguration -ComputerName $computers -UseExisting -Wait -Verbose -Force } diff --git a/Lab/LabData/gittools.gitversion-5.0.1.3.vsix b/Lab/LabData/gittools.gitversion-5.0.1.3.vsix index c591138b..a753a9c1 100644 Binary files a/Lab/LabData/gittools.gitversion-5.0.1.3.vsix and b/Lab/LabData/gittools.gitversion-5.0.1.3.vsix differ diff --git a/Readme.md b/README.md similarity index 100% rename from Readme.md rename to README.md diff --git a/DSC/PSDepend.DscResources.psd1 b/RequiredModules.psd1 similarity index 56% rename from DSC/PSDepend.DscResources.psd1 rename to RequiredModules.psd1 index 28bc022d..c3543938 100644 --- a/DSC/PSDepend.DscResources.psd1 +++ b/RequiredModules.psd1 @@ -1,14 +1,40 @@ @{ PSDependOptions = @{ - AddToPath = $true - Target = 'DscResources' - DependencyType = 'PSGalleryModule' - Parameters = @{ + AddToPath = $true + Target = 'output\RequiredModules' + Parameters = @{ Repository = 'PSGallery' AllowPreRelease = $true } } + 'powershell-yaml' = 'latest' + InvokeBuild = 'latest' + PSScriptAnalyzer = 'latest' + Pester = '4.10.1' + Plaster = 'latest' + ModuleBuilder = 'latest' + ChangelogManagement = 'latest' + Sampler = 'latest' + 'Sampler.GitHubTasks' = 'latest' + PowerShellForGitHub = 'latest' + 'Sampler.DscPipeline' = '0.1.1-preview0001' + MarkdownLinkCheck = 'latest' + 'DscResource.AnalyzerRules' = 'latest' + DscBuildHelpers = 'latest' + Datum = '0.39.0' + ProtectedData = 'latest' + 'Datum.ProtectedData' = 'latest' + 'Datum.InvokeCommand' = 'latest' + ReverseDSC = 'latest' + Configuration = 'latest' + Metadata = 'latest' + xDscResourceDesigner = 'latest' + + # Composites + CommonTasks = '0.3.259' + + # DSC Resources xPSDesiredStateConfiguration = '9.1.0' ComputerManagementDsc = '8.5.0' NetworkingDsc = '8.2.0' @@ -28,7 +54,7 @@ GPRegistryPolicyDsc = '1.2.0' AuditPolicyDsc = '1.4.0.0' SharePointDSC = '4.8.0' - xExchange = '1.32.0' + xExchange = '1.33.0' SqlServerDsc = '15.2.0' UpdateServicesDsc = '1.2.1' xWindowsEventForwarding = '1.0.0.0' diff --git a/Resolve-Dependency.ps1 b/Resolve-Dependency.ps1 new file mode 100644 index 00000000..a8c6b4d1 --- /dev/null +++ b/Resolve-Dependency.ps1 @@ -0,0 +1,421 @@ +<# + .DESCRIPTION + Bootstrap script for PSDepend. + + .PARAMETER DependencyFile + Specifies the configuration file for the this script. The default value is + 'RequiredModules.psd1' relative to this script's path. + + .PARAMETER PSDependTarget + Path for PSDepend to be bootstrapped and save other dependencies. + Can also be CurrentUser or AllUsers if you wish to install the modules in + such scope. The default value is './output/RequiredModules' relative to + this script's path. + + .PARAMETER Proxy + Specifies the URI to use for Proxy when attempting to bootstrap + PackageProvider and PowerShellGet. + + .PARAMETER ProxyCredential + Specifies the credential to contact the Proxy when provided. + + .PARAMETER Scope + Specifies the scope to bootstrap the PackageProvider and PSGet if not available. + THe default value is 'CurrentUser'. + + .PARAMETER Gallery + Specifies the gallery to use when bootstrapping PackageProvider, PSGet and + when calling PSDepend (can be overridden in Dependency files). The default + value is 'PSGallery'. + + .PARAMETER GalleryCredential + Specifies the credentials to use with the Gallery specified above. + + .PARAMETER AllowOldPowerShellGetModule + Allow you to use a locally installed version of PowerShellGet older than + 1.6.0 (not recommended). Default it will install the latest PowerShellGet + if an older version than 2.0 is detected. + + .PARAMETER MinimumPSDependVersion + Allow you to specify a minimum version fo PSDepend, if you're after specific + features. + + .PARAMETER AllowPrerelease + Not yet written. + + .PARAMETER WithYAML + Not yet written. + + .NOTES + Load defaults for parameters values from Resolve-Dependency.psd1 if not + provided as parameter. +#> +[CmdletBinding()] +param +( + [Parameter()] + [System.String] + $DependencyFile = 'RequiredModules.psd1', + + [Parameter()] + [System.String] + $PSDependTarget = (Join-Path -Path $PSScriptRoot -ChildPath './output/RequiredModules'), + + [Parameter()] + [System.Uri] + $Proxy, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ProxyCredential, + + [Parameter()] + [ValidateSet('CurrentUser', 'AllUsers')] + [System.String] + $Scope = 'CurrentUser', + + [Parameter()] + [System.String] + $Gallery = 'PSGallery', + + [Parameter()] + [System.Management.Automation.PSCredential] + $GalleryCredential, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $AllowOldPowerShellGetModule, + + [Parameter()] + [System.String] + $MinimumPSDependVersion, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $AllowPrerelease, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $WithYAML +) + +try +{ + if ($PSVersionTable.PSVersion.Major -le 5) + { + if (-not (Get-Command -Name 'Import-PowerShellDataFile' -ErrorAction 'SilentlyContinue')) + { + Import-Module -Name Microsoft.PowerShell.Utility -RequiredVersion '3.1.0.0' + } + + <# + Making sure the imported PackageManagement module is not from PS7 module + path. The VSCode PS extension is changing the $env:PSModulePath and + prioritize the PS7 path. This is an issue with PowerShellGet because + it loads an old version if available (or fail to load latest). + #> + Get-Module -ListAvailable PackageManagement | + Where-Object -Property 'ModuleBase' -NotMatch 'powershell.7' | + Select-Object -First 1 | + Import-Module -Force + } + + Write-Verbose -Message 'Importing Bootstrap default parameters from ''$PSScriptRoot/Resolve-Dependency.psd1''.' + + $resolveDependencyConfigPath = Join-Path -Path $PSScriptRoot -ChildPath '.\Resolve-Dependency.psd1' -Resolve -ErrorAction 'Stop' + + $resolveDependencyDefaults = Import-PowerShellDataFile -Path $resolveDependencyConfigPath + + $parameterToDefault = $MyInvocation.MyCommand.ParameterSets.Where{ $_.Name -eq $PSCmdlet.ParameterSetName }.Parameters.Keys + + if ($parameterToDefault.Count -eq 0) + { + $parameterToDefault = $MyInvocation.MyCommand.Parameters.Keys + } + + # Set the parameters available in the Parameter Set, or it's not possible to choose yet, so all parameters are an option. + foreach ($parameterName in $parameterToDefault) + { + if (-not $PSBoundParameters.Keys.Contains($parameterName) -and $resolveDependencyDefaults.ContainsKey($parameterName)) + { + Write-Verbose -Message "Setting $parameterName with $($resolveDependencyDefaults[$parameterName])." + + try + { + $variableValue = $resolveDependencyDefaults[$parameterName] + + if ($variableValue -is [System.String]) + { + $variableValue = $ExecutionContext.InvokeCommand.ExpandString($variableValue) + } + + $PSBoundParameters.Add($parameterName, $variableValue) + + Set-Variable -Name $parameterName -value $variableValue -Force -ErrorAction 'SilentlyContinue' + } + catch + { + Write-Verbose -Message "Error adding default for $parameterName : $($_.Exception.Message)." + } + } + } +} +catch +{ + Write-Warning -Message "Error attempting to import Bootstrap's default parameters from '$resolveDependencyConfigPath': $($_.Exception.Message)." +} + +Write-Progress -Activity 'Bootstrap:' -PercentComplete 0 -CurrentOperation 'NuGet Bootstrap' + +# TODO: This should handle the parameter $AllowOldPowerShellGetModule. +$powerShellGetModule = Import-Module -Name 'PowerShellGet' -MinimumVersion '2.0' -ErrorAction 'SilentlyContinue' -PassThru + +# Install the package provider if it is not available. +$nuGetProvider = Get-PackageProvider -Name 'NuGet' -ListAvailable | Select-Object -First 1 + +if (-not $powerShellGetModule -and -not $nuGetProvider) +{ + $providerBootstrapParameters = @{ + Name = 'nuget' + Force = $true + ForceBootstrap = $true + ErrorAction = 'Stop' + } + + switch ($PSBoundParameters.Keys) + { + 'Proxy' + { + $providerBootstrapParameters.Add('Proxy', $Proxy) + } + + 'ProxyCredential' + { + $providerBootstrapParameters.Add('ProxyCredential', $ProxyCredential) + } + + 'Scope' + { + $providerBootstrapParameters.Add('Scope', $Scope) + } + } + + if ($AllowPrerelease) + { + $providerBootstrapParameters.Add('AllowPrerelease', $true) + } + + Write-Information -MessageData 'Bootstrap: Installing NuGet Package Provider from the web (Make sure Microsoft addresses/ranges are allowed).' + + $null = Install-PackageProvider @providerBootstrapParams + + $nuGetProvider = Get-PackageProvider -Name 'NuGet' -ListAvailable | Select-Object -First 1 + + $nuGetProviderVersion = $nuGetProvider.Version.ToString() + + Write-Information -MessageData "Bootstrap: Importing NuGet Package Provider version $nuGetProviderVersion to current session." + + $Null = Import-PackageProvider -Name 'NuGet' -RequiredVersion $nuGetProviderVersion -Force +} + +Write-Progress -Activity 'Bootstrap:' -PercentComplete 10 -CurrentOperation "Ensuring Gallery $Gallery is trusted" + +# Fail if the given PSGallery is not registered. +$previousGalleryInstallationPolicy = (Get-PSRepository -Name $Gallery -ErrorAction 'Stop').InstallationPolicy + +Set-PSRepository -Name $Gallery -InstallationPolicy 'Trusted' -ErrorAction 'Ignore' + +try +{ + Write-Progress -Activity 'Bootstrap:' -PercentComplete 25 -CurrentOperation 'Checking PowerShellGet' + + # Ensure the module is loaded and retrieve the version you have. + $powerShellGetVersion = (Import-Module -Name 'PowerShellGet' -PassThru -ErrorAction 'SilentlyContinue').Version + + Write-Verbose -Message "Bootstrap: The PowerShellGet version is $powerShellGetVersion" + + # Versions below 2.0 are considered old, unreliable & not recommended + if (-not $powerShellGetVersion -or ($powerShellGetVersion -lt [System.Version] '2.0' -and -not $AllowOldPowerShellGetModule)) + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 40 -CurrentOperation 'Installing newer version of PowerShellGet' + + $installPowerShellGetParameters = @{ + Name = 'PowerShellGet' + Force = $True + SkipPublisherCheck = $true + AllowClobber = $true + Scope = $Scope + Repository = $Gallery + } + + switch ($PSBoundParameters.Keys) + { + 'Proxy' + { + $installPowerShellGetParameters.Add('Proxy', $Proxy) + } + + 'ProxyCredential' + { + $installPowerShellGetParameters.Add('ProxyCredential', $ProxyCredential) + } + + 'GalleryCredential' + { + $installPowerShellGetParameters.Add('Credential', $GalleryCredential) + } + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 60 -CurrentOperation 'Installing newer version of PowerShellGet' + + Install-Module @installPowerShellGetParameters + + Remove-Module -Name 'PowerShellGet' -Force -ErrorAction 'SilentlyContinue' + Remove-Module -Name 'PackageManagement' -Force + + $powerShellGetModule = Import-Module PowerShellGet -Force -PassThru + + $powerShellGetVersion = $powerShellGetModule.Version.ToString() + + Write-Information -MessageData "Bootstrap: PowerShellGet version loaded is $powerShellGetVersion" + } + + # Try to import the PSDepend module from the available modules. + $getModuleParameters = @{ + Name = 'PSDepend' + ListAvailable = $true + } + + $psDependModule = Get-Module @getModuleParameters + + if ($PSBoundParameters.ContainsKey('MinimumPSDependVersion')) + { + try + { + $psDependModule = $psDependModule | Where-Object -FilterScript { $_.Version -ge $MinimumPSDependVersion } + } + catch + { + throw ('There was a problem finding the minimum version of PSDepend. Error: {0}' -f $_) + } + } + + if (-not $psDependModule) + { + # PSDepend module not found, installing or saving it. + if ($PSDependTarget -in 'CurrentUser', 'AllUsers') + { + Write-Debug -Message "PSDepend module not found. Attempting to install from Gallery $Gallery." + + Write-Warning -Message "Installing PSDepend in $PSDependTarget Scope." + + $installPSDependParameters = @{ + Name = 'PSDepend' + Repository = $Gallery + Force = $true + Scope = $PSDependTarget + SkipPublisherCheck = $true + AllowClobber = $true + } + + if ($MinimumPSDependVersion) + { + $installPSDependParameters.Add('MinimumVersion', $MinimumPSDependVersion) + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 75 -CurrentOperation "Installing PSDepend from $Gallery" + + Install-Module @installPSDependParameters + } + else + { + Write-Debug -Message "PSDepend module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget" + + $saveModuleParameters = @{ + Name = 'PSDepend' + Repository = $Gallery + Path = $PSDependTarget + Force = $true + } + + if ($MinimumPSDependVersion) + { + $saveModuleParameters.add('MinimumVersion', $MinimumPSDependVersion) + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 75 -CurrentOperation "Saving & Importing PSDepend from $Gallery to $Scope" + + Save-Module @saveModuleParameters + } + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 80 -CurrentOperation 'Loading PSDepend' + + $importModulePSDependParameters = @{ + Name = 'PSDepend' + ErrorAction = 'Stop' + Force = $true + } + + if ($PSBoundParameters.ContainsKey('MinimumPSDependVersion')) + { + $importModulePSDependParameters.Add('MinimumVersion', $MinimumPSDependVersion) + } + + # We should have successfully bootstrapped PSDepend. Fail if not available. + $null = Import-Module @importModulePSDependParameters + + if ($WithYAML) + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 82 -CurrentOperation 'Verifying PowerShell module PowerShell-Yaml' + + if (-not (Get-Module -ListAvailable -Name 'PowerShell-Yaml')) + { + Write-Progress -Activity 'Bootstrap:' -PercentComplete 85 -CurrentOperation 'Installing PowerShell module PowerShell-Yaml' + + Write-Verbose -Message "PowerShell-Yaml module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget" + + $SaveModuleParam = @{ + Name = 'PowerShell-Yaml' + Repository = $Gallery + Path = $PSDependTarget + Force = $true + } + + Save-Module @SaveModuleParam + } + else + { + Write-Verbose "PowerShell-Yaml is already available" + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 88 -CurrentOperation 'Importing PowerShell module PowerShell-Yaml' + + Import-Module -Name 'PowerShell-Yaml' -ErrorAction 'Stop' + } + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoke PSDepend' + + Write-Progress -Activity "PSDepend:" -PercentComplete 0 -CurrentOperation "Restoring Build Dependencies" + + if (Test-Path -Path $DependencyFile) + { + $psDependParameters = @{ + Force = $true + Path = $DependencyFile + } + + # TODO: Handle when the Dependency file is in YAML, and -WithYAML is specified. + Invoke-PSDepend @psDependParameters + } + + Write-Progress -Activity "PSDepend:" -PercentComplete 100 -CurrentOperation "Dependencies restored" -Completed + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 100 -CurrentOperation "Bootstrap complete" -Completed +} +finally +{ + # Reverting the Installation Policy for the given gallery + Set-PSRepository -Name $Gallery -InstallationPolicy $previousGalleryInstallationPolicy + Write-Verbose -Message "Project Bootstrapped, returning to Invoke-Build" +} diff --git a/Resolve-Dependency.psd1 b/Resolve-Dependency.psd1 new file mode 100644 index 00000000..f61799ff --- /dev/null +++ b/Resolve-Dependency.psd1 @@ -0,0 +1,5 @@ +@{ + Gallery = 'PSGallery' + AllowPrerelease = $false + WithYAML = $false # Will also bootstrap PowerShell-Yaml to read other config files +} diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 2934bb7e..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,19 +0,0 @@ -environment: - APPVEYOR_RDP_PASSWORD: Somepass1 - -os: WMF 5 - -version: 0.0.{build} -build_script: -- ps: cd .\DSC -- ps: '[System.Environment]::CurrentDirectory = $PWD' -- ps: .\Build.ps1 -ResolveDependency - -skip_commits: - message: /updated doc.*|update doc.*s/ - -#init: -# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) -# -#on_finish: -# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/azure-pipelines Cloud.yml b/azure-pipelines Cloud.yml deleted file mode 100644 index 3c10a291..00000000 --- a/azure-pipelines Cloud.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: $(BuildID) - -trigger: -- '*' - -pool: - name: Hosted VS2017 - -jobs: -- job: DscBuild - strategy: - parallel: 1 - pool: - name: Default - workspace: - clean: all - steps: - - powershell: '.\Build.ps1 -ResolveDependency' - workingDirectory: DSC - displayName: 'Execute build.ps1' - - - task: PublishTestResults@2 - displayName: 'Publish Integration Test Results' - inputs: - testResultsFormat: NUnit - testResultsFiles: '**/IntegrationTestResults.xml' - failTaskOnFailedTests: true - - - task: PublishBuildArtifacts@1 - displayName: MOF - inputs: - PathtoPublish: '$(Build.SourcesDirectory)\DSC\BuildOutput\MOF' - ArtifactName: MOF - - - task: PublishBuildArtifacts@1 - displayName: 'Meta MOF' - inputs: - PathtoPublish: '$(Build.SourcesDirectory)\DSC\BuildOutput\MetaMOF' - ArtifactName: 'Meta MOF' - - - task: PublishBuildArtifacts@1 - displayName: RSOP - inputs: - PathtoPublish: '$(Build.SourcesDirectory)\DSC\BuildOutput\RSOP' - ArtifactName: RSOP - - - task: PublishBuildArtifacts@1 - condition: and(succeeded(), not(startsWith(variables['BHCommitMessage'], '--Added new node'))) - displayName: 'Compressed Modules' - inputs: - PathtoPublish: '$(Build.SourcesDirectory)\DSC\BuildOutput\CompressedModules' - ArtifactName: 'Compressed Modules' - - - task: PublishTestResults@2 - displayName: 'Publish Build Acceptance Test Results' - inputs: - testResultsFormat: NUnit - testResultsFiles: '**/BuildAcceptanceTestResults.xml' - failTaskOnFailedTests: true diff --git a/azure-pipelines On-Prem.yml b/azure-pipelines On-Prem.yml index 8e45bb84..68840e2e 100644 --- a/azure-pipelines On-Prem.yml +++ b/azure-pipelines On-Prem.yml @@ -1,91 +1,296 @@ -name: $(BuildID) - trigger: -- '*' + branches: + include: + - main + paths: + exclude: + - CHANGELOG.md + tags: + include: + - "v*" + exclude: + - "*-*" variables: - RepositoryUri: ggggg #will be replaced during lab deployment - -jobs: -- job: DscBuild - strategy: - parallel: 1 - pool: - name: Default - workspace: - clean: all - steps: - - task: PowerShell@2 - displayName: Register PowerShell Gallery - inputs: - targetType: inline - script: | - #always make sure the local PowerShell Gallery is registered correctly - [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 - $uri = '$(RepositoryUri)' - $name = 'PowerShell' - $r = Get-PSRepository -Name $name -ErrorAction SilentlyContinue - if (-not $r -or $r.SourceLocation -ne $uri -or $r.PublishLocation -ne $uri) { - Write-Host "The Source or PublishLocation of the repository '$name' is not correct or the repository is not registered" - Unregister-PSRepository -Name $name -ErrorAction SilentlyContinue - Register-PSRepository -Name $name -SourceLocation $uri -PublishLocation $uri -InstallationPolicy Trusted - Get-PSRepository - } - errorActionPreference: stop - - - task: PowerShell@2 - displayName: 'Execute build.ps1' - inputs: - targetType: inline - script: | - Set-Location -Path .\DSC - - [int]$currentJobNumber = $env:SYSTEM_JOBPOSITIONINPHASE - [int]$totalJobCount = $env:SYSTEM_TOTALJOBSINPHASE - Write-Host "Calling 'Build.ps1' with parameter 'CurrentJobNumber' = $currentJobNumber and 'TotalJobCount' = $totalJobCount" - .\Build.ps1 -ResolveDependency -Repository PowerShell -CurrentJobNumber $currentJobNumber -TotalJobCount $totalJobCount - - - task: PublishTestResults@2 - displayName: 'Publish Integration Test Results' - inputs: - testResultsFormat: NUnit - testResultsFiles: '**/IntegrationTestResults.xml' - failTaskOnFailedTests: true - - - task: PublishBuildArtifacts@1 - displayName: MOF - inputs: - PathtoPublish: '$(Build.SourcesDirectory)\DSC\BuildOutput\MOF' - ArtifactName: MOF - - - task: PublishBuildArtifacts@1 - displayName: 'Meta MOF' - inputs: - PathtoPublish: '$(Build.SourcesDirectory)\DSC\BuildOutput\MetaMOF' - ArtifactName: 'Meta MOF' - - - task: PublishBuildArtifacts@1 - displayName: RSOP - inputs: - PathtoPublish: '$(Build.SourcesDirectory)\DSC\BuildOutput\RSOP' - ArtifactName: RSOP - - - task: PublishBuildArtifacts@1 - condition: and(succeeded(), not(startsWith(variables['BHCommitMessage'], '--Added new node'))) - displayName: 'Compressed Modules' - inputs: - PathtoPublish: '$(Build.SourcesDirectory)\DSC\BuildOutput\CompressedModules' - ArtifactName: 'Compressed Modules' - - - task: PublishBuildArtifacts@1 - displayName: 'Build Folder' - inputs: - PathtoPublish: '$(Build.SourcesDirectory)' - ArtifactName: 'SourcesDirectory' - - - task: PublishTestResults@2 - displayName: 'Publish Build Acceptance Test Results' - inputs: - testResultsFormat: NUnit - testResultsFiles: '**/BuildAcceptanceTestResults.xml' - failTaskOnFailedTests: true + buildFolderName: output + buildArtifactName: output + testResultFolderName: testResults + defaultBranch: main + PSModuleFeed: PowerShell + GalleryApiToken: NA + RepositoryUri: RepositoryUri_WillBeChanged #will be replaced during DscWorkshop lab deployment + +stages: + - stage: build + jobs: + - job: Dsc_Build + displayName: 'Build DSC Artifacts' + pool: + name: Default + workspace: + clean: all + steps: + - task: GitVersion@5 + name: gitVersion + displayName: 'Evaluate Next Version' + inputs: + runtime: 'full' + configFilePath: 'GitVersion.yml' + + - task: PowerShell@2 + name: displayEnvironmentVariables + displayName: 'Display Environment Variables' + inputs: + targetType: 'inline' + script: | + dir -Path env: | Out-String | Write-Host + + - task: PowerShell@2 + displayName: Register PowerShell Gallery + inputs: + targetType: inline + script: | + #always make sure the local PowerShell Gallery is registered correctly + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 + $uri = '$(RepositoryUri)' + $name = 'PowerShell' + $r = Get-PSRepository -Name $name -ErrorAction SilentlyContinue + if (-not $r -or $r.SourceLocation -ne $uri -or $r.PublishLocation -ne $uri) { + Write-Host "The Source or PublishLocation of the repository '$name' is not correct or the repository is not registered" + Unregister-PSRepository -Name $name -ErrorAction SilentlyContinue + Register-PSRepository -Name $name -SourceLocation $uri -PublishLocation $uri -InstallationPolicy Trusted + Get-PSRepository + } + + - task: PowerShell@2 + name: build + displayName: 'Build DSC Artifacts' + inputs: + filePath: './build.ps1' + arguments: '-ResolveDependency -tasks build' + env: + ModuleVersion: $(gitVersion.NuGetVersionV2) + + - task: PowerShell@2 + name: pack + displayName: 'Pack DSC Artifacts' + inputs: + filePath: './build.ps1' + arguments: '-ResolveDependency -tasks pack' + env: + ModuleVersion: $(gitVersion.NuGetVersionV2) + + - task: PublishBuildArtifacts@1 + displayName: 'Publish MOF Files' + inputs: + PathtoPublish: '$(buildFolderName)/MOF' + ArtifactName: MOF + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Meta MOF Files' + inputs: + PathtoPublish: '$(buildFolderName)/MetaMOF' + ArtifactName: MetaMOF + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Compressed Modules' + inputs: + PathtoPublish: '$(buildFolderName)/CompressedModules' + ArtifactName: CompressedModules + + - task: PublishBuildArtifacts@1 + displayName: 'Publish RSOP Files' + inputs: + PathtoPublish: '$(buildFolderName)/RSOP' + ArtifactName: RSOP + + - stage: deployArtifactsShare + jobs: + - job: dscDeployArtifactsShare + displayName: 'Deploy to Artifacts Share' + pool: + name: Default + workspace: + clean: all + steps: + + - task: PowerShell@2 + name: DisplayEnvironmentVariables + displayName: 'Display Environment Variables' + inputs: + targetType: 'inline' + script: | + dir -Path env: | Out-String | Write-Host + + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact: MOF' + inputs: + buildType: 'current' + artifactName: MOF + downloadPath: $(Build.SourcesDirectory) + + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact: MetaMof' + inputs: + buildType: 'current' + artifactName: MetaMof + downloadPath: $(Build.SourcesDirectory) + + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact: RSOP' + inputs: + buildType: 'current' + artifactName: RSOP + downloadPath: $(Build.SourcesDirectory) + + #Copy jobs to artifacts share + - task: CopyFiles@2 + name: DeployMofToArtifactsShare + displayName: 'Deploy MOF Files to Artifacts Share' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/MOF' + Contents: '**' + TargetFolder: '\\dscdo01\Artifacts\$(Build.DefinitionName)\$(Build.BuildId)\MOF' + OverWrite: true + + - task: CopyFiles@2 + name: DeployMetaMofToArtifactsShare + displayName: 'Deploy Meta MOF Files to Artifacts Share' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/MetaMOF' + Contents: '**' + TargetFolder: '\\dscdo01\Artifacts\$(Build.DefinitionName)\$(Build.BuildId)\MetaMOF' + OverWrite: true + + - task: CopyFiles@2 + name: DeployRsopToArtifactsShare + displayName: 'Deploy RSOP Files to Artifacts Share' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/RSOP' + Contents: '**' + TargetFolder: '\\dscdo01\Artifacts\$(Build.DefinitionName)\$(Build.BuildId)\RSOP' + OverWrite: true + + - stage: DscDeploymentDev + dependsOn: build + jobs: + - deployment: Dev + displayName: Dev Deployment + environment: Dev + pool: + name: Default + workspace: + clean: all + strategy: + runOnce: + deploy: + steps: + - download: None + + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact: MOF' + inputs: + buildType: 'current' + artifactName: MOF + downloadPath: $(Build.SourcesDirectory) + + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact: CompressedModules' + inputs: + buildType: 'current' + artifactName: CompressedModules + downloadPath: $(Build.SourcesDirectory) + + - task: PowerShell@2 + name: displayEnvironmentVariables + displayName: 'Display Environment Variables' + inputs: + targetType: 'inline' + script: | + dir -Path env: | Out-String | Write-Host + + - task: CopyFiles@2 + name: DeployMofsToPullServer + displayName: 'Deploy MOF Files to Pull Server' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/MOF/$(Environment.Name)' + Contents: '**' + TargetFolder: '\\dscpull01\DscConfiguration' + OverWrite: true + + - task: CopyFiles@2 + name: DeployCompressedModules + displayName: 'Deploy Compressed Modules to Pull Server' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/CompressedModules' + Contents: '**' + TargetFolder: '\\dscpull01\DscModules' + OverWrite: true + + - stage: DscDeploymentTest + dependsOn: + - build + - DscDeploymentDev + jobs: + - deployment: Test + displayName: Test Deployment + environment: Test + pool: + name: Default + workspace: + clean: all + strategy: + runOnce: + deploy: + steps: + - download: None + + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact: MOF' + inputs: + buildType: 'current' + artifactName: MOF + downloadPath: $(Build.SourcesDirectory) + + - task: CopyFiles@2 + name: DeployMofsToPullServer + displayName: 'Deploy MOF Files to Pull Server' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/MOF/$(Environment.Name)' + Contents: '**' + TargetFolder: '\\dscpull01\DscConfiguration' + OverWrite: true + + - stage: DscDeploymentProd + dependsOn: + - build + - DscDeploymentTest + jobs: + - deployment: Prod + displayName: Prodt Deployment + environment: Prod + pool: + name: Default + workspace: + clean: all + strategy: + runOnce: + deploy: + steps: + - download: None + + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifact: MOF' + inputs: + buildType: 'current' + artifactName: MOF + downloadPath: $(Build.SourcesDirectory) + + - task: CopyFiles@2 + name: DeployMofsToPullServer + displayName: 'Deploy MOF Files to Pull Server' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/MOF/$(Environment.Name)' + Contents: '**' + TargetFolder: '\\dscpull01\DscConfiguration' + OverWrite: true diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..d969f248 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,90 @@ +trigger: + branches: + include: + - '*' + paths: + exclude: + - CHANGELOG.md + tags: + include: + - "v*" + exclude: + - "*-*" + +variables: + buildFolderName: output + testResultFolderName: testResults + defaultBranch: main + +stages: + - stage: Build + jobs: + - job: Compile_Dsc + displayName: 'Compile DSC Configuration' + pool: + vmImage: 'windows-2019' + steps: + + - task: GitVersion@5 + name: gitVersion + displayName: 'Evaluate Next Version' + inputs: + runtime: 'core' + configFilePath: 'GitVersion.yml' + + - task: PowerShell@2 + name: build + displayName: 'Build DSC Artifacts' + inputs: + filePath: './build.ps1' + arguments: '-ResolveDependency -tasks build' + pwsh: false + env: + ModuleVersion: $(gitVersion.NuGetVersionV2) + + - task: PowerShell@2 + name: pack + displayName: 'Pack DSC Artifacts' + inputs: + filePath: './build.ps1' + arguments: '-ResolveDependency -tasks pack' + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Output Folder' + inputs: + targetPath: '$(buildFolderName)/' + artifact: 'output' + publishLocation: 'pipeline' + parallel: true + + - task: PublishPipelineArtifact@1 + displayName: 'Publish MOF Files' + inputs: + targetPath: '$(buildFolderName)/MOF' + artifact: 'MOF' + publishLocation: 'pipeline' + parallel: true + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Meta MOF Files' + inputs: + targetPath: '$(buildFolderName)/MetaMOF' + artifact: 'MetaMOF' + publishLocation: 'pipeline' + parallel: true + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Compressed Modules' + inputs: + targetPath: '$(buildFolderName)/CompressedModules' + artifact: 'CompressedModules' + publishLocation: 'pipeline' + parallel: true + + - task: PublishPipelineArtifact@1 + displayName: 'Publish RSOP Files' + inputs: + targetPath: '$(buildFolderName)/RSOP' + artifact: 'RSOP' + publishLocation: 'pipeline' + parallel: true diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 00000000..83a05350 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,517 @@ +<# + .DESCRIPTION + Bootstrap and build script for PowerShell module CI/CD pipeline + + .PARAMETER Tasks + The task or tasks to run. The default value is '.' (runs the default task). + + .PARAMETER CodeCoverageThreshold + The code coverage target threshold to uphold. Set to 0 to disable. + The default value is '' (empty string). + + .PARAMETER BuildConfig + Not yet written. + + .PARAMETER OutputDirectory + Specifies the folder to build the artefact into. The default value is 'output'. + + .PARAMETER BuiltModuleSubdirectory + Subdirectory name to build the module (under $OutputDirectory). The default + value is '' (empty string). + + .PARAMETER RequiredModulesDirectory + Can be a path (relative to $PSScriptRoot or absolute) to tell Resolve-Dependency + and PSDepend where to save the required modules. It is also possible to use + 'CurrentUser' och 'AllUsers' to install missing dependencies. You can override + the value for PSDepend in the Build.psd1 build manifest. The default value is + 'output/RequiredModules'. + + .PARAMETER PesterScript + One or more paths that will override the Pester configuration in build + configuration file when running the build task Invoke_Pester_Tests. + + If running Pester 5 test, use the alias PesterPath to be future-proof. + + .PARAMETER PesterTag + Filter which tags to run when invoking Pester tests. This is used in the + Invoke-Pester.pester.build.ps1 tasks. + + .PARAMETER PesterExcludeTag + Filter which tags to exclude when invoking Pester tests. This is used in + the Invoke-Pester.pester.build.ps1 tasks. + + .PARAMETER DscTestTag + Filter which tags to run when invoking DSC Resource tests. This is used + in the DscResource.Test.build.ps1 tasks. + + .PARAMETER DscTestExcludeTag + Filter which tags to exclude when invoking DSC Resource tests. This is + used in the DscResource.Test.build.ps1 tasks. + + .PARAMETER ResolveDependency + Not yet written. + + .PARAMETER BuildInfo + The build info object from ModuleBuilder. Defaults to an empty hashtable. + + .PARAMETER AutoRestore + Not yet written. +#> +[CmdletBinding()] +param +( + [Parameter(Position = 0)] + [System.String[]] + $Tasks = '.', + + [Parameter(Position = 1)] + [ScriptBlock] + $Filter = {}, + + [Parameter()] + [System.String] + $CodeCoverageThreshold = '', + + [Parameter()] + [System.String] + [ValidateScript( + { Test-Path -Path $_ } + )] + $BuildConfig, + + [Parameter()] + [System.String] + $OutputDirectory = 'output', + + [Parameter()] + [System.String] + $BuiltModuleSubdirectory = '', + + [Parameter()] + [System.String] + $RequiredModulesDirectory = $(Join-Path 'output' 'RequiredModules'), + + [Parameter()] + # This alias is to prepare for the rename of this parameter to PesterPath when Pester 4 support is removed + [Alias('PesterPath')] + [System.Object[]] + $PesterScript, + + [Parameter()] + [System.String[]] + $PesterTag, + + [Parameter()] + [System.String[]] + $PesterExcludeTag, + + [Parameter()] + [System.String[]] + $DscTestTag, + + [Parameter()] + [System.String[]] + $DscTestExcludeTag, + + [Parameter()] + [Alias('bootstrap')] + [System.Management.Automation.SwitchParameter] + $ResolveDependency, + + [Parameter(DontShow)] + [AllowNull()] + [System.Collections.Hashtable] + $BuildInfo, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $AutoRestore +) + +<# + The BEGIN block (at the end of this file) handles the Bootstrap of the Environment + before Invoke-Build can run the tasks if the parameter ResolveDependency (or + parameter alias Bootstrap) is specified. +#> + +process +{ + + if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1') + { + # Only run the process block through InvokeBuild (look at the Begin block at the bottom of this script). + return + } + + # Execute the Build process from the .build.ps1 path. + Push-Location -Path $PSScriptRoot -StackName 'BeforeBuild' + + try + { + Write-Host -Object "[build] Parsing defined tasks" -ForeGroundColor Magenta + + # Load the default BuildInfo if the parameter BuildInfo is not set. + if (-not $PSBoundParameters.ContainsKey('BuildInfo')) + { + try + { + if (Test-Path -Path $BuildConfig) + { + $configFile = Get-Item -Path $BuildConfig + + Write-Host -Object "[build] Loading Configuration from $configFile" + + $BuildInfo = switch -Regex ($configFile.Extension) + { + # Native Support for PSD1 + '\.psd1' + { + if (-not (Get-Command -Name Import-PowerShellDataFile -ErrorAction SilentlyContinue)) + { + Import-Module -Name Microsoft.PowerShell.Utility -RequiredVersion 3.1.0.0 + } + + Import-PowerShellDataFile -Path $BuildConfig + } + + # Support for yaml when module PowerShell-Yaml is available + '\.[yaml|yml]' + { + Import-Module -Name 'powershell-yaml' -ErrorAction Stop + + ConvertFrom-Yaml -Yaml (Get-Content -Raw $configFile) + } + + # Native Support for JSON and JSONC (by Removing comments) + '\.[json|jsonc]' + { + $jsonFile = Get-Content -Raw -Path $configFile + + $jsonContent = $jsonFile -replace '(?m)\s*//.*?$' -replace '(?ms)/\*.*?\*/' + + # Yaml is superset of JSON. + ConvertFrom-Yaml -Yaml $jsonContent + } + + # Unknown extension, return empty hashtable. + default + { + Write-Error -Message "Extension '$_' not supported. using @{}" + + @{ } + } + } + } + else + { + Write-Host -Object "Configuration file '$($BuildConfig.FullName)' not found" -ForegroundColor Red + + # No config file was found, return empty hashtable. + $BuildInfo = @{ } + } + } + catch + { + $logMessage = "Error loading Config '$($BuildConfig.FullName)'.`r`nAre you missing dependencies?`r`nMake sure you run './build.ps1 -ResolveDependency -tasks noop' before running build to restore the required modules." + + Write-Host -Object $logMessage -ForegroundColor Yellow + + $BuildInfo = @{ } + + Write-Error -Message $_.Exception.Message + } + } + + # If the Invoke-Build Task Header is specified in the Build Info, set it. + if ($BuildInfo.TaskHeader) + { + Set-BuildHeader -Script ([scriptblock]::Create($BuildInfo.TaskHeader)) + } + + <# + Add BuildModuleOutput to PSModule Path environment variable. + Moved here (not in begin block) because build file can contains BuiltSubModuleDirectory value. + #> + if ($BuiltModuleSubdirectory) + { + if (-not (Split-Path -IsAbsolute -Path $BuiltModuleSubdirectory)) + { + $BuildModuleOutput = Join-Path -Path $OutputDirectory -ChildPath $BuiltModuleSubdirectory + } + else + { + $BuildModuleOutput = $BuiltModuleSubdirectory + } + } # test if BuiltModuleSubDirectory set in build config file + elseif ($BuildInfo.ContainsKey('BuiltModuleSubDirectory')) + { + $BuildModuleOutput = Join-Path -Path $OutputDirectory -ChildPath $BuildInfo['BuiltModuleSubdirectory'] + } + else + { + $BuildModuleOutput = $OutputDirectory + } + + # Pre-pending $BuildModuleOutput folder to PSModulePath to resolve built module from this folder. + if ($powerShellModulePaths -notcontains $BuildModuleOutput) + { + Write-Host -Object "[build] Pre-pending '$BuildModuleOutput' folder to PSModulePath" -ForegroundColor Green + + $env:PSModulePath = $BuildModuleOutput + [System.IO.Path]::PathSeparator + $env:PSModulePath + } + + <# + Import Tasks from modules via their exported aliases when defined in Build Manifest. + https://github.com/nightroman/Invoke-Build/tree/master/Tasks/Import#example-2-import-from-a-module-with-tasks + #> + if ($BuildInfo.ContainsKey('ModuleBuildTasks')) + { + foreach ($module in $BuildInfo['ModuleBuildTasks'].Keys) + { + try + { + Write-Host -Object "Importing tasks from module $module" -ForegroundColor DarkGray + + $loadedModule = Import-Module -Name $module -PassThru -ErrorAction Stop + + foreach ($TaskToExport in $BuildInfo['ModuleBuildTasks'].($module)) + { + $loadedModule.ExportedAliases.GetEnumerator().Where{ + Write-Host -Object "`t Loading $($_.Key)..." -ForegroundColor DarkGray + + # Using -like to support wildcard. + $_.Key -like $TaskToExport + }.ForEach{ + # Dot-sourcing the Tasks via their exported aliases. + . (Get-Alias $_.Key) + } + } + } + catch + { + Write-Host -Object "Could not load tasks for module $module." -ForegroundColor Red + + Write-Error -Message $_ + } + } + } + + # Loading Build Tasks defined in the .build/ folder (will override the ones imported above if same task name). + Get-ChildItem -Path '.build/' -Recurse -Include '*.ps1' -ErrorAction Ignore | + ForEach-Object { + "Importing file $($_.BaseName)" | Write-Verbose + + . $_.FullName + } + + # Synopsis: Empty task, useful to test the bootstrap process. + task noop { } + + # Define default task sequence ("."), can be overridden in the $BuildInfo. + task . { + Write-Build -Object 'No sequence currently defined for the default task' -ForegroundColor Yellow + } + + Write-Host -Object 'Adding Workflow from configuration:' -ForegroundColor DarkGray + + # Load Invoke-Build task sequences/workflows from $BuildInfo. + foreach ($workflow in $BuildInfo.BuildWorkflow.keys) + { + Write-Verbose -Message "Creating Build Workflow '$Workflow' with tasks $($BuildInfo.BuildWorkflow.($Workflow) -join ', ')." + + $workflowItem = $BuildInfo.BuildWorkflow.($workflow) + + if ($workflowItem.Trim() -match '^\{(?[\w\W]*)\}$') + { + $workflowItem = [ScriptBlock]::Create($Matches['sb']) + } + + Write-Host -Object " +-> $workflow" -ForegroundColor DarkGray + + task $workflow $workflowItem + } + + Write-Host -Object "[build] Executing requested workflow: $($Tasks -join ', ')" -ForeGroundColor Magenta + + } + finally + { + Pop-Location -StackName 'BeforeBuild' + } +} + +Begin +{ + # Find build config if not specified. + if (-not $BuildConfig) + { + $config = Get-ChildItem -Path "$PSScriptRoot\*" -Include 'build.y*ml', 'build.psd1', 'build.json*' -ErrorAction Ignore + + if (-not $config -or ($config -is [System.Array] -and $config.Length -le 0)) + { + throw 'No build configuration found. Specify path via parameter BuildConfig.' + } + elseif ($config -is [System.Array]) + { + if ($config.Length -gt 1) + { + throw 'More than one build configuration found. Specify which path to use via parameter BuildConfig.' + } + + $BuildConfig = $config[0] + } + else + { + $BuildConfig = $config + } + } + + # Bootstrapping the environment before using Invoke-Build as task runner + + if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') + { + Write-Host -Object "[pre-build] Starting Build Init" -ForegroundColor Green + + Push-Location $PSScriptRoot -StackName 'BuildModule' + } + + if ($RequiredModulesDirectory -in @('CurrentUser', 'AllUsers')) + { + # Installing modules instead of saving them. + Write-Host -Object "[pre-build] Required Modules will be installed to the PowerShell module path that is used for $RequiredModulesDirectory." -ForegroundColor Green + + <# + The variable $PSDependTarget will be used below when building the splatting + variable before calling Resolve-Dependency.ps1, unless overridden in the + file Resolve-Dependency.psd1. + #> + $PSDependTarget = $RequiredModulesDirectory + } + else + { + if (-not (Split-Path -IsAbsolute -Path $OutputDirectory)) + { + $OutputDirectory = Join-Path -Path $PSScriptRoot -ChildPath $OutputDirectory + } + + # Resolving the absolute path to save the required modules to. + if (-not (Split-Path -IsAbsolute -Path $RequiredModulesDirectory)) + { + $RequiredModulesDirectory = Join-Path -Path $PSScriptRoot -ChildPath $RequiredModulesDirectory + } + + # Create the output/modules folder if not exists, or resolve the Absolute path otherwise. + if (Resolve-Path -Path $RequiredModulesDirectory -ErrorAction SilentlyContinue) + { + Write-Debug -Message "[pre-build] Required Modules path already exist at $RequiredModulesDirectory" + + $requiredModulesPath = Convert-Path -Path $RequiredModulesDirectory + } + else + { + Write-Host -Object "[pre-build] Creating required modules directory $RequiredModulesDirectory." -ForegroundColor Green + + $requiredModulesPath = (New-Item -ItemType Directory -Force -Path $RequiredModulesDirectory).FullName + } + + $powerShellModulePaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator + + # Pre-pending $requiredModulesPath folder to PSModulePath to resolve from this folder FIRST. + if ($RequiredModulesDirectory -notin @('CurrentUser', 'AllUsers') -and + ($powerShellModulePaths -notcontains $RequiredModulesDirectory)) + { + Write-Host -Object "[pre-build] Pre-pending '$RequiredModulesDirectory' folder to PSModulePath" -ForegroundColor Green + + $env:PSModulePath = $RequiredModulesDirectory + [System.IO.Path]::PathSeparator + $env:PSModulePath + } + + $powerShellYamlModule = Get-Module -Name 'powershell-yaml' -ListAvailable + $invokeBuildModule = Get-Module -Name 'InvokeBuild' -ListAvailable + $psDependModule = Get-Module -Name 'PSDepend' -ListAvailable + + # Checking if the user should -ResolveDependency. + if (-not ($powerShellYamlModule -and $invokeBuildModule -and $psDependModule) -and -not $ResolveDependency) + { + if ($AutoRestore -or -not $PSBoundParameters.ContainsKey('Tasks') -or $Tasks -contains 'build') + { + Write-Host -Object "[pre-build] Dependency missing, running './build.ps1 -ResolveDependency -Tasks noop' for you `r`n" -ForegroundColor Yellow + + $ResolveDependency = $true + } + else + { + Write-Warning -Message "Some required Modules are missing, make sure you first run with the '-ResolveDependency' parameter. Running 'build.ps1 -ResolveDependency -Tasks noop' will pull required modules without running the build task." + } + } + + <# + The variable $PSDependTarget will be used below when building the splatting + variable before calling Resolve-Dependency.ps1, unless overridden in the + file Resolve-Dependency.psd1. + #> + $PSDependTarget = $requiredModulesPath + } + + if ($ResolveDependency) + { + Write-Host -Object "[pre-build] Resolving dependencies." -ForegroundColor Green + $resolveDependencyParams = @{ } + + # If BuildConfig is a Yaml file, bootstrap powershell-yaml via ResolveDependency. + if ($BuildConfig -match '\.[yaml|yml]$') + { + $resolveDependencyParams.Add('WithYaml', $true) + } + + $resolveDependencyAvailableParams = (Get-Command -Name '.\Resolve-Dependency.ps1').Parameters.Keys + + foreach ($cmdParameter in $resolveDependencyAvailableParams) + { + # The parameter has been explicitly used for calling the .build.ps1 + if ($MyInvocation.BoundParameters.ContainsKey($cmdParameter)) + { + $paramValue = $MyInvocation.BoundParameters.ContainsKey($cmdParameter) + + Write-Debug " adding $cmdParameter :: $paramValue [from user-provided parameters to Build.ps1]" + + $resolveDependencyParams.Add($cmdParameter, $paramValue) + } + # Use defaults parameter value from Build.ps1, if any + else + { + $paramValue = Get-Variable -Name $cmdParameter -ValueOnly -ErrorAction Ignore + + if ($paramValue) + { + Write-Debug " adding $cmdParameter :: $paramValue [from default Build.ps1 variable]" + + $resolveDependencyParams.Add($cmdParameter, $paramValue) + } + } + } + + Write-Host -Object "[pre-build] Starting bootstrap process." -ForegroundColor Green + + .\Resolve-Dependency.ps1 @resolveDependencyParams + } + + if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') + { + Write-Verbose -Message "Bootstrap completed. Handing back to InvokeBuild." + + if ($PSBoundParameters.ContainsKey('ResolveDependency')) + { + Write-Verbose -Message "Dependency already resolved. Removing task." + + $null = $PSBoundParameters.Remove('ResolveDependency') + } + + Write-Host -Object "[build] Starting build with InvokeBuild." -ForegroundColor Green + + Invoke-Build @PSBoundParameters -Task $Tasks -File $MyInvocation.MyCommand.Path + + Pop-Location -StackName 'BuildModule' + + return + } +} diff --git a/build.yaml b/build.yaml new file mode 100644 index 00000000..d917dfdc --- /dev/null +++ b/build.yaml @@ -0,0 +1,96 @@ +--- +BuiltModuleSubDirectory: AvoidPSPathOverlap +#################################################### +# Sampler Pipeline Configuration # +#################################################### +# Defining 'Workflows' (suite of InvokeBuild tasks) to be run using their alias +BuildWorkflow: + '.': # "." is the default Invoke-Build workflow. It is called when no -Tasks is specified to the build.ps1 + - build + - pack + + init: | + { + if ($PSVersionTable.PSEdition -ne 'Desktop') { + Write-Error "The build script required Windows PowerShell 5.1 to work" + } + } + + build: + - Init + - Clean + - LoadDatumConfigData + - TestConfigData + - CompileDatumRsop + - TestDscResources + - CompileRootConfiguration + - CompileRootMetaMof + + pack: + - Init + - LoadDatumConfigData + - NewMofChecksums + - CompressModulesWithChecksum + - Compress_Artifact_Collections + - TestBuildAcceptance + +#################################################### +# PESTER Configuration # +#################################################### + +Pester: + OutputFormat: NUnitXML + # Excludes one or more paths from being used to calculate code coverage. + ExcludeFromCodeCoverage: + + # If no scripts are defined the default is to use all the tests under the project's + # tests folder or source folder (if present). Test script paths can be defined to + # only run tests in certain folders, or run specific test files, or can be use to + # specify the order tests are run. + Script: + # - tests/QA/module.tests.ps1 + # - tests/QA + # - tests/Unit + # - tests/Integration + ExcludeTag: + # - helpQuality + # - FunctionalQuality + # - TestQuality + Tag: + CodeCoverageThreshold: 0 # Set to 0 to bypass + #CodeCoverageOutputFile: JaCoCo_.xml + #CodeCoverageOutputFileEncoding: ascii + # Use this if code coverage should be merged from several pipeline test jobs. + # Any existing keys above should be replaced. + # CodeCoverageOutputFile - the file that is created for each pipeline test job. + # CodeCoverageFilePattern - the pattern used to search all pipeline test job artifacts + # after the file specified in CodeCoverageOutputFile. + # CodeCoverageMergedOutputFile - the file that is created by the merge build task and + # is the file that should be uploaded to code coverage services. + #CodeCoverageOutputFile: JaCoCo_Merge.xml + #CodeCoverageMergedOutputFile: JaCoCo_coverage.xml + #CodeCoverageFilePattern: JaCoCo_Merge.xml + + +# Import ModuleBuilder tasks from a specific PowerShell module using the build +# task's alias. Wildcard * can be used to specify all tasks that has a similar +# prefix and or suffix. The module contain the task must be added as a required +# module in the file RequiredModules.psd1. +ModuleBuildTasks: + Sampler: + - '*.build.Sampler.ib.tasks' + Sampler.DscPipeline: + - '*.ib.tasks' + + +# Invoke-Build Header to be used to 'decorate' the terminal output of the tasks. +TaskHeader: | + param($Path) + "" + "=" * 79 + Write-Build Cyan "`t`t`t$($Task.Name.replace("_"," ").ToUpper())" + Write-Build DarkGray "$(Get-BuildSynopsis $Task)" + "-" * 79 + Write-Build DarkGray " $Path" + Write-Build DarkGray " $($Task.InvocationInfo.ScriptName):$($Task.InvocationInfo.ScriptLineNumber)" + "" diff --git a/DSC/DscConfigData/AllNodes/Dev/DSCFile01.yml b/source/AllNodes/Dev/DSCFile01.yml similarity index 86% rename from DSC/DscConfigData/AllNodes/Dev/DSCFile01.yml rename to source/AllNodes/Dev/DSCFile01.yml index 93a08c44..2e064615 100644 --- a/DSC/DscConfigData/AllNodes/Dev/DSCFile01.yml +++ b/source/AllNodes/Dev/DSCFile01.yml @@ -11,7 +11,7 @@ ComputerSettings: NetworkIpConfiguration: Interfaces: - - InterfaceAlias: Ethernet + - InterfaceAlias: DscWorkshop 0 IpAddress: 192.168.111.100 Prefix: 24 Gateway: 192.168.111.50 @@ -35,3 +35,5 @@ FilesAndFolders: Items: - DestinationPath: Z:\DoesNotWork Type: Directory + - DestinationPath: C:\Test\211209 DscWorkshop + Type: Directory diff --git a/DSC/DscConfigData/AllNodes/Dev/DSCWeb01.yml b/source/AllNodes/Dev/DSCWeb01.yml similarity index 93% rename from DSC/DscConfigData/AllNodes/Dev/DSCWeb01.yml rename to source/AllNodes/Dev/DSCWeb01.yml index ce5fe03e..df7c5de9 100644 --- a/DSC/DscConfigData/AllNodes/Dev/DSCWeb01.yml +++ b/source/AllNodes/Dev/DSCWeb01.yml @@ -10,7 +10,7 @@ ComputerSettings: NetworkIpConfiguration: Interfaces: - - InterfaceAlias: Ethernet + - InterfaceAlias: DscWorkshop 0 IpAddress: 192.168.111.101 Prefix: 24 Gateway: 192.168.111.50 diff --git a/DSC/DscConfigData/AllNodes/Prod/DSCFile03.yml b/source/AllNodes/Prod/DSCFile03.yml similarity index 94% rename from DSC/DscConfigData/AllNodes/Prod/DSCFile03.yml rename to source/AllNodes/Prod/DSCFile03.yml index 24c7c1e5..9eff8ee8 100644 --- a/DSC/DscConfigData/AllNodes/Prod/DSCFile03.yml +++ b/source/AllNodes/Prod/DSCFile03.yml @@ -11,7 +11,7 @@ ComputerSettings: NetworkIpConfiguration: Interfaces: - - InterfaceAlias: Ethernet + - InterfaceAlias: DscWorkshop 0 IpAddress: 192.168.111.120 Prefix: 24 Gateway: 192.168.111.50 diff --git a/DSC/DscConfigData/AllNodes/Prod/DSCWeb03.yml b/source/AllNodes/Prod/DSCWeb03.yml similarity index 94% rename from DSC/DscConfigData/AllNodes/Prod/DSCWeb03.yml rename to source/AllNodes/Prod/DSCWeb03.yml index 4bfaf77c..6b3d597a 100644 --- a/DSC/DscConfigData/AllNodes/Prod/DSCWeb03.yml +++ b/source/AllNodes/Prod/DSCWeb03.yml @@ -11,7 +11,7 @@ ComputerSettings: NetworkIpConfiguration: Interfaces: - - InterfaceAlias: Ethernet + - InterfaceAlias: DscWorkshop 0 IpAddress: 192.168.111.121 Prefix: 24 Gateway: 192.168.111.50 diff --git a/DSC/DscConfigData/AllNodes/Test/DSCFile02.yml b/source/AllNodes/Test/DSCFile02.yml similarity index 94% rename from DSC/DscConfigData/AllNodes/Test/DSCFile02.yml rename to source/AllNodes/Test/DSCFile02.yml index d4f049c6..cdb87c57 100644 --- a/DSC/DscConfigData/AllNodes/Test/DSCFile02.yml +++ b/source/AllNodes/Test/DSCFile02.yml @@ -11,7 +11,7 @@ ComputerSettings: NetworkIpConfiguration: Interfaces: - - InterfaceAlias: Ethernet + - InterfaceAlias: DscWorkshop 0 IpAddress: 192.168.111.110 Prefix: 24 Gateway: 192.168.111.50 diff --git a/DSC/DscConfigData/AllNodes/Test/DSCWeb02.yml b/source/AllNodes/Test/DSCWeb02.yml similarity index 94% rename from DSC/DscConfigData/AllNodes/Test/DSCWeb02.yml rename to source/AllNodes/Test/DSCWeb02.yml index b64e3b4d..e22c21be 100644 --- a/DSC/DscConfigData/AllNodes/Test/DSCWeb02.yml +++ b/source/AllNodes/Test/DSCWeb02.yml @@ -11,7 +11,7 @@ ComputerSettings: NetworkIpConfiguration: Interfaces: - - InterfaceAlias: Ethernet + - InterfaceAlias: DscWorkshop 0 IpAddress: 192.168.111.111 Prefix: 24 Gateway: 192.168.111.50 diff --git a/DSC/DscConfigData/Baselines/DscLcm.yml b/source/Baselines/DscLcm.yml similarity index 100% rename from DSC/DscConfigData/Baselines/DscLcm.yml rename to source/Baselines/DscLcm.yml diff --git a/DSC/DscConfigData/Baselines/Security.yml b/source/Baselines/Security.yml similarity index 100% rename from DSC/DscConfigData/Baselines/Security.yml rename to source/Baselines/Security.yml diff --git a/DSC/DscConfigData/Baselines/Server.yml b/source/Baselines/Server.yml similarity index 100% rename from DSC/DscConfigData/Baselines/Server.yml rename to source/Baselines/Server.yml diff --git a/DSC/DscConfigData/Datum.yml b/source/Datum.yml similarity index 100% rename from DSC/DscConfigData/Datum.yml rename to source/Datum.yml diff --git a/DSC/DscConfigData/Environment/Dev.yml b/source/Environment/Dev.yml similarity index 100% rename from DSC/DscConfigData/Environment/Dev.yml rename to source/Environment/Dev.yml diff --git a/DSC/DscConfigData/Environment/Prod.yml b/source/Environment/Prod.yml similarity index 100% rename from DSC/DscConfigData/Environment/Prod.yml rename to source/Environment/Prod.yml diff --git a/DSC/DscConfigData/Environment/Test.yml b/source/Environment/Test.yml similarity index 100% rename from DSC/DscConfigData/Environment/Test.yml rename to source/Environment/Test.yml diff --git a/DSC/DscConfigData/ExternalData.csv b/source/ExternalData.csv similarity index 100% rename from DSC/DscConfigData/ExternalData.csv rename to source/ExternalData.csv diff --git a/DSC/DscConfigData/Locations/Frankfurt.yml b/source/Locations/Frankfurt.yml similarity index 100% rename from DSC/DscConfigData/Locations/Frankfurt.yml rename to source/Locations/Frankfurt.yml diff --git a/DSC/DscConfigData/Locations/London.yml b/source/Locations/London.yml similarity index 100% rename from DSC/DscConfigData/Locations/London.yml rename to source/Locations/London.yml diff --git a/DSC/DscConfigData/Locations/Singapore.yml b/source/Locations/Singapore.yml similarity index 100% rename from DSC/DscConfigData/Locations/Singapore.yml rename to source/Locations/Singapore.yml diff --git a/DSC/DscConfigData/Locations/Tokio.yml b/source/Locations/Tokio.yml similarity index 100% rename from DSC/DscConfigData/Locations/Tokio.yml rename to source/Locations/Tokio.yml diff --git a/DSC/DscConfigData/Roles/DomainController.yml b/source/Roles/DomainController.yml similarity index 100% rename from DSC/DscConfigData/Roles/DomainController.yml rename to source/Roles/DomainController.yml diff --git a/DSC/DscConfigData/Roles/FileServer.yml b/source/Roles/FileServer.yml similarity index 100% rename from DSC/DscConfigData/Roles/FileServer.yml rename to source/Roles/FileServer.yml diff --git a/DSC/DscConfigData/Roles/WebServer.yml b/source/Roles/WebServer.yml similarity index 100% rename from DSC/DscConfigData/Roles/WebServer.yml rename to source/Roles/WebServer.yml diff --git a/DSC/RootConfiguration.ps1 b/source/RootConfiguration.ps1 similarity index 90% rename from DSC/RootConfiguration.ps1 rename to source/RootConfiguration.ps1 index 770e42a0..19641914 100644 --- a/DSC/RootConfiguration.ps1 +++ b/source/RootConfiguration.ps1 @@ -1,9 +1,8 @@ Import-Module -Name DscBuildHelpers $Error.Clear() -$buildVersion = $env:BHBuildVersion -if (-not $buildVersion) { - $buildVersion = '0.0.0' +if (-not $ModuleVersion) { + $ModuleVersion = '0.0.0' } $environment = $node.Environment @@ -19,16 +18,16 @@ configuration "RootConfiguration" $module = Get-Module -Name PSDesiredStateConfiguration & $module { param( - [string]$BuildVersion, + [string]$ModuleVersion, [string]$Environment - ) - $Script:PSTopConfigurationName = "MOF_$($Environment)_$($BuildVersion)" - } $buildVersion, $environment + ) + $Script:PSTopConfigurationName = "MOF_$($Environment)_$($ModuleVersion)" + } $ModuleVersion, $environment node $ConfigurationData.AllNodes.NodeName { Write-Host "`r`n$('-'*75)`r`n$($Node.Name) : $($Node.NodeName) : $(&$module { $Script:PSTopConfigurationName })" -ForegroundColor Yellow - - $configurationNames = Resolve-NodeProperty -PropertyPath 'Configurations' + + $configurationNames = Resolve-NodeProperty -PropertyPath 'Configurations' -Node $Node $global:node = $node #this makes the node variable being propagated into the configurations foreach ($configurationName in $configurationNames) { @@ -36,9 +35,9 @@ configuration "RootConfiguration" $properties = Resolve-NodeProperty -PropertyPath $configurationName -DefaultValue @{} $dscError = [System.Collections.ArrayList]::new() - + (Get-DscSplattedResource -ResourceName $configurationName -ExecutionName $configurationName -Properties $properties -NoInvoke).Invoke($properties) - + if($Error[0] -and $lastError -ne $Error[0]) { $lastIndex = [Math]::Max(($Error.LastIndexOf($lastError) -1), -1) if($lastIndex -gt 0) { @@ -71,6 +70,7 @@ configuration "RootConfiguration" } } } + $global:node = $node = $null } $cd = @{} @@ -81,7 +81,8 @@ foreach ($node in $configurationData.AllNodes) $cd.AllNodes = @($ConfigurationData.AllNodes | Where-Object NodeName -eq $node.NodeName) try { - RootConfiguration -ConfigurationData $cd -OutputPath (Join-Path -Path $BuildOutput -ChildPath MOF) + $path = Join-Path -Path MOF -ChildPath $node.Environment + RootConfiguration -ConfigurationData $cd -OutputPath (Join-Path -Path $BuildOutput -ChildPath $path) } catch { diff --git a/DSC/RootMetaMof.ps1 b/source/RootMetaMof.ps1 similarity index 78% rename from DSC/RootMetaMof.ps1 rename to source/RootMetaMof.ps1 index 7339a298..25a13ba9 100644 --- a/DSC/RootMetaMof.ps1 +++ b/source/RootMetaMof.ps1 @@ -3,43 +3,53 @@ Import-Module DscBuildHelpers [DscLocalConfigurationManager()] Configuration RootMetaMOF { Node $ConfigurationData.AllNodes.GetEnumerator().NodeName { - - $lcmConfig = Resolve-NodeProperty -PropertyPath LcmConfig\Settings -DefaultValue $null + + $lcmConfig = Resolve-NodeProperty -PropertyPath LcmConfig\Settings -Node $Node -DefaultValue $null #If the Nodename is a GUID, use Config ID instead Named config, as per SMB Pull requirements - if ($Node.Nodename -as [Guid]) { + if ($Node.Nodename -as [Guid]) + { $lcmConfig['ConfigurationID'] = $Node.Nodename } (Get-DscSplattedResource -ResourceName Settings -ExecutionName '' -Properties $lcmConfig -NoInvoke).Invoke($lcmConfig) - if ($configurationRepositoryShare = Resolve-NodeProperty -PropertyPath 'LcmConfig\ConfigurationRepositoryShare' -DefaultValue $null) { + if ($configurationRepositoryShare = Resolve-NodeProperty -PropertyPath 'LcmConfig\ConfigurationRepositoryShare' -Node $Node -DefaultValue $null) + { (Get-DscSplattedResource -ResourceName ConfigurationRepositoryShare -ExecutionName ConfigurationRepositoryShare -Properties $configurationRepositoryShare -NoInvoke).Invoke($configurationRepositoryShare) } - if ($resourceRepositoryShare = Resolve-NodeProperty -PropertyPath 'LcmConfig\ResourceRepositoryShare' -DefaultValue $null) { + if ($resourceRepositoryShare = Resolve-NodeProperty -PropertyPath 'LcmConfig\ResourceRepositoryShare' -Node $Node -DefaultValue $null) + { (Get-DscSplattedResource -ResourceName ResourceRepositoryShare -ExecutionName ResourceRepositoryShare -Properties $resourceRepositoryShare -NoInvoke).Invoke($resourceRepositoryShare) } - if ($configurationRepositoryWeb = Resolve-NodeProperty -PropertyPath 'LcmConfig\ConfigurationRepositoryWeb' -DefaultValue $null) { - foreach ($configRepoName in $configurationRepositoryWeb.Keys) { + if ($configurationRepositoryWeb = Resolve-NodeProperty -PropertyPath 'LcmConfig\ConfigurationRepositoryWeb' -Node $Node -DefaultValue $null) + { + foreach ($configRepoName in $configurationRepositoryWeb.Keys) + { (Get-DscSplattedResource -ResourceName ConfigurationRepositoryWeb -ExecutionName $configRepoName -Properties $configurationRepositoryWeb[$configRepoName] -NoInvoke).Invoke($configurationRepositoryWeb[$configRepoName]) } } - if ($resourceRepositoryWeb = Resolve-NodeProperty -PropertyPath 'LcmConfig\ResourceRepositoryWeb' -DefaultValue $null) { - foreach ($resourceRepoName in $resourceRepositoryWeb.Keys) { + if ($resourceRepositoryWeb = Resolve-NodeProperty -PropertyPath 'LcmConfig\ResourceRepositoryWeb' -Node $Node -DefaultValue $null) + { + foreach ($resourceRepoName in $resourceRepositoryWeb.Keys) + { (Get-DscSplattedResource -ResourceName ResourceRepositoryWeb -ExecutionName $resourceRepoName -Properties $resourceRepositoryWeb[$resourceRepoName] -NoInvoke).Invoke($resourceRepositoryWeb[$resourceRepoName]) } } - if ($reportServerWeb = Resolve-NodeProperty -PropertyPath 'LcmConfig\ReportServerWeb' -DefaultValue $null) { + if ($reportServerWeb = Resolve-NodeProperty -PropertyPath 'LcmConfig\ReportServerWeb' -Node $Node -DefaultValue $null) + { (Get-DscSplattedResource -ResourceName ReportServerWeb -ExecutionName ReportServerWeb -Properties $reportServerWeb -NoInvoke).Invoke($reportServerWeb) } - if ($partialConfiguration = Resolve-NodeProperty -PropertyPath 'LcmConfig\PartialConfiguration' -DefaultValue $null) { - foreach ($partialConfigurationName in $partialConfiguration.Keys) { + if ($partialConfiguration = Resolve-NodeProperty -PropertyPath 'LcmConfig\PartialConfiguration' -Node $Node -DefaultValue $null) + { + foreach ($partialConfigurationName in $partialConfiguration.Keys) + { (Get-DscSplattedResource -ResourceName PartialConfiguration -ExecutionName $partialConfigurationName -Properties $partialConfiguration[$partialConfigurationName] -NoInvoke).Invoke($partialConfiguration[$partialConfigurationName]) } } - + } } diff --git a/tests/Acceptance/TestMofFiles.Tests.ps1 b/tests/Acceptance/TestMofFiles.Tests.ps1 new file mode 100644 index 00000000..d93125db --- /dev/null +++ b/tests/Acceptance/TestMofFiles.Tests.ps1 @@ -0,0 +1,53 @@ +$here = $PSScriptRoot +if ($global:Filter -and $global:Filter.ToString() -and -not $Filter.ToString()) +{ + $Filter = $global:Filter +} + +$datumDefinitionFile = Join-Path $here ..\..\source\Datum.yml +$nodeDefinitions = Get-ChildItem $here\..\..\source\AllNodes -Recurse -Include *.yml +$environments = (Get-ChildItem $here\..\..\source\AllNodes -Directory).BaseName +$roleDefinitions = Get-ChildItem $here\..\..\source\Roles -Recurse -Include *.yml +$datum = New-DatumStructure -DefinitionFile $datumDefinitionFile +$configurationData = Get-FilteredConfigurationData -Filter $Filter -CurrentJobNumber $currentJobNumber -TotalJobCount $totalJobCount + +$nodeNames = [System.Collections.ArrayList]::new() + +Describe 'MOF Files' -Tag BuildAcceptance { + BeforeAll { + $mofFiles = Get-ChildItem -Path "$OutputDirectory\MOF" -Filter *.mof -Recurse -ErrorAction SilentlyContinue + $metaMofFiles = Get-ChildItem -Path "$OutputDirectory\MetaMOF" -Filter *.mof -ErrorAction SilentlyContinue + $nodes = $configurationData.AllNodes + } + + It 'All nodes have a MOF file' { + Write-Verbose "MOF File Count $($mofFiles.Count)" + Write-Verbose "Node Count $($nodes.Count)" + + $mofFiles.Count | Should -Be $nodes.Count + } + + foreach ($node in $nodes) + { + It "Node '$($node.NodeName)' should have a MOF file" { + $mofFiles | Where-Object BaseName -eq $node.NodeName | Should -BeOfType System.IO.FileSystemInfo + } + } + + if ($metaMofFiles) + { + It 'All nodes have a Meta MOF file' { + Write-Verbose "Meta MOF File Count $($metaMofFiles.Count)" + Write-Verbose "Node Count $($nodes.Count)" + + $metaMofFiles.Count | Should -BeIn $nodes.Count + } + + foreach ($node in $nodes) + { + It "Node '$($node.NodeName)' should have a Meta MOF file" { + $metaMofFiles | Where-Object BaseName -eq "$($node.NodeName).meta" | Should -BeOfType System.IO.FileSystemInfo + } + } + } +} diff --git a/DSC/Tests/ConfigData/ConfigData.Tests.ps1 b/tests/ConfigData/ConfigData.Tests.ps1 similarity index 68% rename from DSC/Tests/ConfigData/ConfigData.Tests.ps1 rename to tests/ConfigData/ConfigData.Tests.ps1 index dabd1523..9a2daf21 100644 --- a/DSC/Tests/ConfigData/ConfigData.Tests.ps1 +++ b/tests/ConfigData/ConfigData.Tests.ps1 @@ -1,16 +1,25 @@ $here = $PSScriptRoot -$datumDefinitionFile = "$ProjectPath\DscConfigData\Datum.yml" -$nodeDefinitions = Get-ChildItem $ProjectPath\DscConfigData\AllNodes -Recurse -Include *.yml -$environments = (Get-ChildItem $ProjectPath\DscConfigData\AllNodes -Directory).BaseName -$roleDefinitions = Get-ChildItem $ProjectPath\DscConfigData\Roles -Recurse -Include *.yml +$datumDefinitionFile = "$ProjectPath\source\Datum.yml" +$nodeDefinitions = Get-ChildItem $ProjectPath\source\AllNodes -Recurse -Include *.yml +$environments = (Get-ChildItem $ProjectPath\source\AllNodes -Directory).BaseName +$roleDefinitions = Get-ChildItem $ProjectPath\source\Roles -Recurse -Include *.yml $datum = New-DatumStructure -DefinitionFile $datumDefinitionFile -$allDefinitions = Get-ChildItem $ProjectPath\DscConfigData -Recurse -Include *.yml -$configurationData = try { - Get-FilteredConfigurationData -Datum $datum -Filter $filter +$allDefinitions = Get-ChildItem $ProjectPath\source -Recurse -Include *.yml +$configurationData = try +{ + if ($filter) + { + Get-FilteredConfigurationData -Filter $filter + } + else + { + Get-FilteredConfigurationData + } } -catch { - Write-Error "'Get-FilteredConfigurationData' did not return any data. Please check if all YAML files are valid and don't have syntax errors" +catch +{ + Write-Error "'Get-FilteredConfigurationData' did not return any data. Please check if all YAML files are valid and don't have syntax errors. $($_.Exception.Message)" } $nodeNames = [System.Collections.ArrayList]::new() @@ -28,7 +37,7 @@ Describe 'Validate All Definition Files' -Tag Integration { } Describe 'Datum Tree Definition' -Tag Integration { - It 'Exists in DscConfigData Folder' { + It 'Exists in source Folder' { Test-Path $datumDefinitionFile | Should -Be $true } @@ -44,16 +53,27 @@ Describe 'Datum Tree Definition' -Tag Integration { } Describe 'Node Definition Files' -Tag Integration { - $environments = Get-ChildItem .\DscConfigData\Environment\ | Select-Object -ExpandProperty BaseName - $locations = Get-ChildItem .\DscConfigData\Locations\ | Select-Object -ExpandProperty BaseName + $environments = Get-ChildItem .\source\Environment\ | Select-Object -ExpandProperty BaseName + $locations = Get-ChildItem .\source\Locations\ | Select-Object -ExpandProperty BaseName + + Context 'Testing for conflicts / duplicate data' { + It 'Should not have duplicate node names' { + $nodes = Get-DatumNodesRecursive -AllDatumNodes $datum.AllNodes + $nodeNames = $nodes.NodeName + $uniqueNodeNames = $nodes.NodeName | Sort-Object -Unique + + (Compare-Object -ReferenceObject $nodeNames -DifferenceObject $uniqueNodeNames).InputObject | Should -BeNullOrEmpty + } + } $nodeDefinitions.ForEach{ # A Node cannot be empty $content = Get-Content -Path $_ -Raw $node = $content | ConvertFrom-Yaml $nodeName = $node.NodeName - - if ($_.BaseName -ne 'AllNodes') { + + if ($_.BaseName -ne 'AllNodes') + { It "'$($_.FullName)' should not be duplicated" { $nodeNames -contains $_.BaseName | Should -Be $false } @@ -83,25 +103,29 @@ Describe 'Node Definition Files' -Tag Integration { } Describe 'Roles Definition Files' -Tag Integration { - $nodes = if ($Environment) { + $nodes = if ($Environment) + { $configurationData.AllNodes | Where-Object { $_.NodeName -ne '*' -and $_.Environment -eq $Environment } } - else { + else + { $configurationData.AllNodes | Where-Object { $_.NodeName -ne '*' } } $nodeRoles = $nodes | ForEach-Object -MemberName Role - $usedRolesDefinitions = foreach ($nodeRole in $nodeRoles) { + $usedRolesDefinitions = foreach ($nodeRole in $nodeRoles) + { $roleDefinitions.Where( { $_.FullName -like "*$($nodeRole)*" }) } $usedRolesDefinitions = $usedRolesDefinitions | Group-Object -Property FullName | ForEach-Object { $_.Group[0] } - + $usedRolesDefinitions.Foreach{ # A role can be Empty $content = Get-Content -Path $_ -Raw - if ($content) { + if ($content) + { It "$($_.FullName) has valid yaml" { { $null = $content | ConvertFrom-Yaml } | Should -Not -Throw } @@ -110,17 +134,21 @@ Describe 'Roles Definition Files' -Tag Integration { } Describe 'Role Composition' -Tag Integration { - foreach ($environment in $environments) { + foreach ($environment in $environments) + { Context "Nodes for environment $environment" { - - $nodes = if ($Environment) { + + $nodes = if ($Environment) + { $configurationData.AllNodes | Where-Object { $_.NodeName -ne '*' -and $_.Environment -eq $Environment } } - else { + else + { $configurationData.AllNodes | Where-Object { $_.NodeName -ne '*' } } - foreach ($node in $nodes) { + foreach ($node in $nodes) + { It "$($node.Name) has a valid Configurations Setting (!`$null)" { { Lookup Configurations -Node $node -DatumTree $datum } | Should -Not -Throw } diff --git a/DSC/Tests/README.md b/tests/README.md similarity index 100% rename from DSC/Tests/README.md rename to tests/README.md