DSC PullServer automation with GitLab

Prerequisites

  1. Install a Pullserver
  2. Install GitLab runner on the PullServer with the shell executor and tags "shell", "powershell5", "windows" and "pullserver".
  3. Register the Gitlab runner/pullServer with your DSC configuration project in GitLab.
  4. Add a .gitignore file to ignore the build output and compiled mof
  5. Add .gitlab-ci.yml file to your project with the following contents
stages:
  - dependencies
  - test
  - run


manage_dependencies:
  stage: dependencies
  tags:
    - shell  # <-- executor, must change config.toml shell from "pwsh" to "powershell" 
    - powershell5 # <-- powershell version`
    - windows # <-- run only on windows
    - pullserver # <-- run only on pullserver
  script: |
    # Configure Environment
    $ErrorActionPreference = 'Stop'
    Set-Location -Path $env:CI_PROJECT_DIR


    # Initialize variables
    [string[]]$detectedmodules = @()
    [string[]]$stagedmodules = @()
    [string[]]$ignoremodules = "PSDesiredStateConfiguration"
    [string]$modulespath = 'C:\Program Files\WindowsPowerShell\DscService\Modules'
    write-warning -Message "This script does not upgrade existing modules. Please test your scripts with modules already installed on the pull server."


    write-output "Setting PSModulePath..."
    $env:PSModulePath = 'C:\Program Files\WindowsPowerShell\Modules;C:\Program Files (x86)\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules'


    Write-Output "Detecting required modules..."
    Get-Content -Path "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE).ps1" | Where-Object {$_ -match '(import-dscresource\s+)'} | ForEach-Object {
        $detectedmodules += $_.ToString().Trim().Split(" ") | Select-Object -Last 1
    }
    $detectedmodules = $detectedmodules | Where-Object{$_ -notcontains $ignoremodules}


    Write-Output "Getting currently staged modules..."
    Get-ChildItem -Path $modulespath -Filter *.zip | ForEach-Object {
        $stagedmodules += $_.Name.ToString().Trim().Split("_")[0]
    }


    Write-Output "Checking for modules in the runners's powershell library..."
    foreach($detectedmodule In $detectedmodules){
        write-output "Checking for $detectedmodule..."
        $checkmod = Get-DscResource -Module $detectedmodule -WarningAction SilentlyContinue
        If($checkmod){
            write-output "$detectedmodule already installed."
        }else{
            Write-Output "Installing $detectedmodule..."
            Install-Module -Name $detectedmodule
        }
    }


    Write-Output "Identifying modules missing from the pullserver repository..."
    $missingmodules = $detectedmodules | Where {$stagedmodules -NotContains $_}


    foreach($missingmodule In $missingmodules){
        Write-Output "Downloading $missingmodule..."
        Save-Module -Name $missingmodule -Path .

        Write-Output "Unhiding files to enable compression..."
        Get-ChildItem -path ".\$missingmodule\$version\*" -force -Recurse | where{$_.Attributes -match "hidden"} | foreach{$_.Attributes=""}

        Write-Output "Compressing module..."
        $version = (Get-ChildItem -Path ".\$missingmodule").Name
        $archive = '.\' + $missingmodule + '_' + $version + '.zip'
        Compress-Archive -Path ".\$missingmodule\$version\*" -DestinationPath $archive

        Write-Output "Creating checksum..."
        New-DscChecksum -Path $archive -OutPath .

        write-output "Copying modules to module share..."
        Copy-Item -Path $archive -Destination $modulespath

        write-output "Copying checksum to module share..."
        Copy-Item -Path "$archive.checksum" -Destination $modulespath
    }

  only:
    - pushes

test_dsc:
  stage: test
  tags:
    - shell
    - powershell5
    - windows
    - pullserver
  script: |
    BeforeAll { 
      . "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE).ps1"
    }

    Describe 'Pester-Test' {
      It 'Passes Script Analyzer' {
        Invoke-ScriptAnalyzer -Path "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE).ps1" -Severity Error | Should -BeNullOrEmpty
      }

      It 'Generated MOF' {
        "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE)\localhost.mof" | Should -Exist
      }
    }

    AfterAll {
      If(Test-Path -Path "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE)\localhost.mof"){Remove-Item -Path "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE)\localhost.mof"}
    }

  only:
    - pushes

run_dsc:
  stage: run
  tags:
    - shell
    - powershell5
    - windows
    - pullserver
  script: |
    # Configure Environment
    $ErrorActionPreference = 'Stop'
    Set-Location -Path $env:CI_PROJECT_DIR


    # Initialize variables
    [string]$configpath = 'C:\Program Files\WindowsPowerShell\DscService\Configuration'


    write-output "Setting PSModulePath..."
    $env:PSModulePath = 'C:\Program Files\WindowsPowerShell\Modules;C:\Program Files (x86)\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules'


    Write-Output "Writing MOF..."
    Try{
        & "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE).ps1" -Verbose
    }Catch{
        Throw $_.Exception.Message
    }


    Write-Output "Creating Checksum..."
    New-DscChecksum -Path "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE)\localhost.mof"


    Write-Output "Renaming MOF file..."
    Rename-Item -Path "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE)\localhost.mof" -NewName "$($env:CI_PROJECT_TITLE).mof"


    Write-Output "Renaming checksum file..."
    Rename-Item -Path "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE)\localhost.mof.checksum" -NewName "$($env:CI_PROJECT_TITLE).mof.checksum"


    Write-Output "Copying MOF to $configpath..."
    Copy-Item -Path "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE)\$($env:CI_PROJECT_TITLE).mof" -Destination $configpath


    Write-Output "Copying checksum to $configpath..."
    Copy-Item -Path "$($env:CI_PROJECT_DIR)\$($env:CI_PROJECT_TITLE)\$($env:CI_PROJECT_TITLE).mof.checksum" -Destination $configpath

  only:
    - master

Document Actions