DSCResources/cOctopusServer/cOctopusServer.psm1
$ErrorActionPreference = "Stop" $installStateFile = "$($env:SystemDrive)\Octopus\Octopus.Server.DSC.installstate" $octopusServerExePath = "$($env:ProgramFiles)\Octopus Deploy\Octopus\Octopus.Server.exe" function Get-TargetResource { [OutputType([Hashtable])] param ( [ValidateSet("Present", "Absent")] [string]$Ensure = "Present", [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Name, [ValidateSet("Started", "Stopped")] [string]$State = "Started", [ValidateNotNullOrEmpty()] [string]$DownloadUrl = "https://octopus.com/downloads/latest/WindowsX64/OctopusServer", [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$WebListenPrefix, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$SqlDbConnectionString, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential]$OctopusAdminCredential, [bool]$AllowUpgradeCheck = $true, [bool]$AllowCollectionOfAnonymousUsageStatistics = $true, [ValidateSet("UsernamePassword", "Domain", "Ignore")] [string]$LegacyWebAuthenticationMode = 'Ignore', [bool]$ForceSSL = $false, [int]$ListenPort = 10943, [bool]$AutoLoginEnabled = $false, [PSCredential]$OctopusServiceCredential, [string]$HomeDirectory, [string]$LicenseKey = $null ) Write-Verbose "Checking if Octopus Server is installed" $installLocation = (Get-ItemProperty -path "HKLM:\Software\Octopus\OctopusServer" -ErrorAction SilentlyContinue).InstallLocation $present = ($null -ne $installLocation) Write-Verbose "Octopus Server present: $present" $existingEnsure = if ($present) { "Present" } else { "Absent" } $serviceName = (Get-ServiceName $Name) Write-Verbose "Checking for Windows Service: $serviceName" $serviceInstance = Get-Service -Name $serviceName -ErrorAction SilentlyContinue $existingState = "Stopped" if ($null -ne $serviceInstance) { Write-Verbose "Windows service: $($serviceInstance.Status)" if ($serviceInstance.Status -eq "Running") { $existingState = "Started" } if ($existingEnsure -eq "Absent") { Write-Verbose "Since the Windows Service is still installed, the service is present" $existingEnsure = "Present" } } else { Write-Verbose "Windows service: Not installed" $existingEnsure = "Absent" } $existingDownloadUrl = $null $existingWebListenPrefix = $null $existingSqlDbConnectionString = $null $existingForceSSL = $null $existingOctopusUpgradesAllowChecking = $null $existingOctopusUpgradesIncludeStatistics = $null $existingListenPort = $null $existingOctopusAdminCredential = $null $existingAutoLoginEnabled = $null $existingOctopusServiceCredential = $null $existingHomeDirectory = $null if ($existingEnsure -eq "Present") { $existingConfig = Import-ServerConfig "$($env:SystemDrive)\Octopus\OctopusServer.config" $Name $existingSqlDbConnectionString = $existingConfig.OctopusStorageExternalDatabaseConnectionString $existingWebListenPrefix = $existingConfig.OctopusWebPortalListenPrefixes $existingForceSSL = $existingConfig.OctopusWebPortalForceSsl $existingOctopusUpgradesAllowChecking = $existingConfig.OctopusUpgradesAllowChecking $existingOctopusUpgradesIncludeStatistics = $existingConfig.OctopusUpgradesIncludeStatistics $existingListenPort = $existingConfig.OctopusCommunicationsServicesPort $existingAutoLoginEnabled = $existingConfig.OctopusWebPortalAutoLoginEnabled $existingLegacyWebAuthenticationMode = $existingConfig.OctopusWebPortalAuthenticationMode $existingHomeDirectory = $existingConfig.OctopusHome if ($existingConfig.OctopusLicenseKey -eq "<unknown>") { $existingLicenseKey = $LicenseKey #if we weren't able to determine the existing key, assume its correct } else { $existingLicenseKey = $existingConfig.OctopusLicenseKey } if (Test-Path $installStateFile) { $installState = (Get-Content -Raw -Path $installStateFile | ConvertFrom-Json) $existingDownloadUrl = $installState.DownloadUrl $pass = $installState.OctopusAdminPassword | ConvertTo-SecureString if ($installState.OctopusServicePassword -ne "") { $svcpass = $installState.OctopusServicePassword | ConvertTo-SecureString $existingOctopusServiceCredential = New-Object System.Management.Automation.PSCredential ($installState.OctopusServiceUsername, $svcpass) } else { $svcpass = "" $existingOctopusServiceCredential = [PSCredential]::Empty } $existingOctopusAdminCredential = New-Object System.Management.Automation.PSCredential ($installState.OctopusAdminUsername, $pass) } } $currentResource = @{ Name = $Name; Ensure = $existingEnsure; State = $existingState; DownloadUrl = $existingDownloadUrl; WebListenPrefix = $existingWebListenPrefix; SqlDbConnectionString = $existingSqlDbConnectionString; ForceSSL = $existingForceSSL; AllowUpgradeCheck = $existingOctopusUpgradesAllowChecking; AllowCollectionOfAnonymousUsageStatistics = $AllowCollectionOfAnonymousUsageStatistics; ListenPort = $existingListenPort; OctopusAdminCredential = $existingOctopusAdminCredential; LegacyWebAuthenticationMode = $existingLegacyWebAuthenticationMode; AutoLoginEnabled = $existingAutoLoginEnabled; OctopusServiceCredential = $existingOctopusServiceCredential; HomeDirectory = $existingHomeDirectory; LicenseKey = $existingLicenseKey; } return $currentResource } function Import-ServerConfig { [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $Path, [Parameter(Mandatory)] [string] $InstanceName ) Write-Verbose "Importing server configuration file from '$Path'" if (-not (Test-Path -LiteralPath $Path)) { throw "Config path '$Path' does not exist." } $file = Get-Item -LiteralPath $Path -ErrorAction Stop if ($file -isnot [System.IO.FileInfo]) { throw "Config path '$Path' does not refer to a file." } if (Test-OctopusVersionSupportsShowConfiguration) { $rawConfig = & $octopusServerExePath show-configuration --format=json-hierarchical --noconsolelogging --console --instance $InstanceName $config = $rawConfig | ConvertFrom-Json # show-configuration only added support for the license from 4.1.3 # unfortunately, $null implies that its the free license, so it would trigger a change every time DSC runs if ([bool]($config.Octopus.Server.psobject.properties.name -match 'License')) { $license = $config.Octopus.Server.License } else { $license = '<unknown>' } $result = [pscustomobject] @{ OctopusStorageExternalDatabaseConnectionString = $config.Octopus.Storage.ExternalDatabaseConnectionString OctopusWebPortalListenPrefixes = $config.Octopus.WebPortal.ListenPrefixes OctopusWebPortalForceSsl = [System.Convert]::ToBoolean($config.Octopus.WebPortal.ForceSSL) OctopusUpgradesAllowChecking = [System.Convert]::ToBoolean($config.Octopus.Upgrades.AllowChecking) OctopusUpgradesIncludeStatistics = [System.Convert]::ToBoolean($config.Octopus.Upgrades.IncludeStatistics) OctopusCommunicationsServicesPort = $config.Octopus.Communications.ServicesPort OctopusWebPortalAuthenticationMode = "Ignore" OctopusWebPortalAutoLoginEnabled = [System.Convert]::ToBoolean($config.Octopus.WebPortal.AutoLoginEnabled) OctopusHomeDirectory = $config.Octopus.Home OctopusLicenseKey = $license } } else { $xml = New-Object xml try { $xml.Load($file.FullName) } catch { throw } $result = [pscustomobject] @{ OctopusStorageExternalDatabaseConnectionString = $xml.SelectSingleNode('/octopus-settings/set[@key="Octopus.Storage.ExternalDatabaseConnectionString"]/text()').Value OctopusWebPortalListenPrefixes = $xml.SelectSingleNode('/octopus-settings/set[@key="Octopus.WebPortal.ListenPrefixes"]/text()').Value OctopusWebPortalForceSsl = [System.Convert]::ToBoolean($xml.SelectSingleNode('/octopus-settings/set[@key="Octopus.WebPortal.ForceSsl"]/text()').Value) OctopusUpgradesAllowChecking = [System.Convert]::ToBoolean($xml.SelectSingleNode('/octopus-settings/set[@key="Octopus.Upgrades.AllowChecking"]/text()').Value) OctopusUpgradesIncludeStatistics = [System.Convert]::ToBoolean($xml.SelectSingleNode('/octopus-settings/set[@key="Octopus.Upgrades.IncludeStatistics"]/text()').Value) OctopusCommunicationsServicesport = $xml.SelectSingleNode('/octopus-settings/set[@key="Octopus.Communications.ServicesPort"]/text()').Value OctopusWebPortalAuthenticationMode = $xml.SelectSingleNode('/octopus-settings/set[@key="Octopus.WebPortal.AuthenticationMode"]/text()').Value OctopusWebPortalAutoLoginEnabled = $xml.SelectSingleNode('/octopus-settings/set[@key="Octopus.WebPortal.AutoLoginEnabled"]/text()').Value OctopusHomeDirectory = $xml.SelectSingleNode('/octopus-settings/set[@key="Octopus.Home"]/text()').Value OctopusLicenseKey = '<unknown>' # we have no easy way to get this } if ($result.OctopusWebPortalAuthenticationMode -eq '0') { $result.OctopusWebPortalAuthenticationMode = 'UsernamePassword' } elseif ($result.OctopusWebPortalAuthenticationMode -eq '1') { $result.OctopusWebPortalAuthenticationMode = 'Domain' } } return $result } function Test-OctopusVersionSupportsAutoLoginEnabled { return Test-OctopusVersionNewerThan (New-Object System.Version 3, 5, 0) } function Test-OctopusVersionSupportsShowConfiguration { return Test-OctopusVersionNewerThan (New-Object System.Version 3, 5, 0) } function Test-OctopusVersionSupportsHomeDirectoryDuringCreateInstance { return Test-OctopusVersionNewerThan (New-Object System.Version 3, 16, 4) } Function Test-OctopusVersionRequiresDatabaseBeforeConfigure { return Test-OctopusVersionNewerThan (New-Object System.Version 4, 0, 0) } function Test-OctopusVersionNewerThan($targetVersion) { if (-not (Test-Path -LiteralPath $octopusServerExePath)) { throw "Octopus.Server.exe path '$octopusServerExePath' does not exist." } $exeFile = Get-Item -LiteralPath $octopusServerExePath -ErrorAction Stop if ($exeFile -isnot [System.IO.FileInfo]) { throw "Octopus.Server.exe path '$octopusServerExePath ' does not refer to a file." } $fileVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($octopusServerExePath).FileVersion $octopusServerVersion = New-Object System.Version $fileVersion return ($octopusServerVersion -ge $targetVersion) } function Set-TargetResource { #The Write-Verbose calls are in other methods [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")] param ( [ValidateSet("Present", "Absent")] [string]$Ensure = "Present", [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Name, [ValidateSet("Started", "Stopped")] [string]$State = "Started", [ValidateNotNullOrEmpty()] [string]$DownloadUrl = "https://octopus.com/downloads/latest/WindowsX64/OctopusServer", [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$WebListenPrefix, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$SqlDbConnectionString, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential]$OctopusAdminCredential, [bool]$AllowUpgradeCheck = $true, [bool]$AllowCollectionOfAnonymousUsageStatistics = $true, [ValidateSet("UsernamePassword", "Domain", "Ignore")] [string]$LegacyWebAuthenticationMode = 'Ignore', [bool]$ForceSSL = $false, [int]$ListenPort = 10943, [bool]$AutoLoginEnabled = $false, [PSCredential]$OctopusServiceCredential, [string]$HomeDirectory, [string]$LicenseKey = $null ) $currentResource = (Get-TargetResource -Ensure $Ensure ` -Name $Name ` -State $State ` -DownloadUrl $DownloadUrl ` -WebListenPrefix $WebListenPrefix ` -SqlDbConnectionString $SqlDbConnectionString ` -OctopusAdminCredential $OctopusAdminCredential ` -AllowUpgradeCheck $AllowUpgradeCheck ` -AllowCollectionOfAnonymousUsageStatistics $AllowCollectionOfAnonymousUsageStatistics ` -LegacyWebAuthenticationMode $LegacyWebAuthenticationMode ` -ForceSSL $ForceSSL ` -ListenPort $ListenPort ` -AutoLoginEnabled $AutoLoginEnabled ` -OctopusServiceCredential $OctopusServiceCredential ` -HomeDirectory $HomeDirectory ` -LicenseKey $LicenseKey) $params = Get-ODSCParameter $MyInvocation.MyCommand.Parameters Test-RequestedConfiguration $currentResource $params if ($State -eq "Stopped" -and $currentResource["State"] -eq "Started") { Stop-OctopusDeployService $Name } if ($Ensure -eq "Absent" -and $currentResource["Ensure"] -eq "Present") { Uninstall-OctopusDeploy $Name } elseif ($Ensure -eq "Present" -and $currentResource["Ensure"] -eq "Absent") { Install-OctopusDeploy -name $Name ` -downloadUrl $DownloadUrl ` -webListenPrefix $WebListenPrefix ` -sqlDbConnectionString $SqlDbConnectionString ` -OctopusAdminCredential $OctopusAdminCredential ` -allowUpgradeCheck $AllowUpgradeCheck ` -allowCollectionOfAnonymousUsageStatistics $AllowCollectionOfAnonymousUsageStatistics ` -legacyWebAuthenticationMode $LegacyWebAuthenticationMode ` -forceSSL $ForceSSL ` -listenPort $ListenPort ` -autoLoginEnabled $AutoLoginEnabled ` -homeDirectory $HomeDirectory ` -octopusServiceCredential $OctopusServiceCredential ` -licenseKey $LicenseKey } else { if ($Ensure -eq "Present" -and $currentResource["DownloadUrl"] -ne $DownloadUrl) { Update-OctopusDeploy -name $Name ` -downloadUrl $DownloadUrl ` -state $State ` -webListenPrefix $webListenPrefix } if (Test-ReconfigurationRequired $currentResource $params) { Set-OctopusDeployConfiguration ` -currentState $currentResource ` -name $Name ` -webListenPrefix $WebListenPrefix ` -allowUpgradeCheck $AllowUpgradeCheck ` -allowCollectionOfAnonymousUsageStatistics $AllowCollectionOfAnonymousUsageStatistics ` -legacyWebAuthenticationMode $LegacyWebAuthenticationMode ` -forceSSL $ForceSSL ` -listenPort $ListenPort ` -autoLoginEnabled $AutoLoginEnabled ` -homeDirectory $HomeDirectory ` -octopusServiceCredential $OctopusServiceCredential ` -licenseKey $LicenseKey } } if ($State -eq "Started" -and $currentResource["State"] -eq "Stopped") { Start-OctopusDeployService -name $Name -webListenPrefix $webListenPrefix } } function Test-RequestedConfiguration($currentState, $desiredState) { if ($desiredState.Item('Ensure') -eq "Absent" -and $desiredState.Item('State') -eq "Started") { throw "Invalid configuration requested. " + ` "You have asked for the service to not exist, but also be running at the same time. " + ` "You probably want 'State = `"Stopped`"." } if ($currentState.Item['Ensure'] -eq "Present") { if (Test-OctopusVersionSupportsShowConfiguration) { if ($desiredState.Item('LegacyWebAuthenticationMode') -ne 'Ignore') { #todo: add note to use new auth resources throw "LegacyWebAuthenticationMode is only supported for Octopus versions older than 3.5.0." } } else { if ($desiredState.Item('LegacyWebAuthenticationMode') -eq 'Ignore') { throw "LegacyWebAuthenticationMode = 'ignore' is only supported from Octopus 3.5.0." } } } } function Set-OctopusDeployConfiguration { param ( [Parameter(Mandatory = $True)] [Hashtable]$currentState, [Parameter(Mandatory = $True)] [string]$name, [Parameter(Mandatory = $True)] [string]$webListenPrefix, [Parameter(Mandatory)] [bool]$allowUpgradeCheck = $true, [bool]$allowCollectionOfAnonymousUsageStatistics = $true, [ValidateSet("UsernamePassword", "Domain", "Ignore")] [string]$legacyWebAuthenticationMode = 'Ignore', [bool]$forceSSL = $false, [int]$listenPort = 10943, [bool]$autoLoginEnabled = $false, [string]$homeDirectory = $null, [PSCredential]$OctopusServiceCredential, [string]$licenseKey = $null ) Write-Log "Configuring Octopus Deploy instance ..." $args = @( 'configure', '--console', '--instance', $name, '--upgradeCheck', $allowUpgradeCheck, '--upgradeCheckWithStatistics', $allowCollectionOfAnonymousUsageStatistics, '--webForceSSL', $forceSSL, '--webListenPrefixes', $webListenPrefix, '--commsListenPort', $listenPort ) if (($homeDirectory -ne "") -and ($null -ne $homeDirectory)) { $args += @('--home', $homeDirectory) } if (Test-OctopusVersionSupportsAutoLoginEnabled) { $args += @('--autoLoginEnabled', $autoLoginEnabled) } else { if (-not ($autoLoginEnabled)) { throw "AutoLoginEnabled is only supported from Octopus 3.5.0." } } if (Test-OctopusVersionSupportsShowConfiguration) { if ($legacyWebAuthenticationMode -ne 'Ignore') { #todo: add note to use new auth resources throw "LegacyWebAuthenticationMode is only supported for Octopus versions older than 3.5.0." } } else { if ($legacyWebAuthenticationMode -eq 'Ignore') { throw "LegacyWebAuthenticationMode = 'ignore' is only supported from Octopus 3.5.0." } $args += @('--webAuthenticationMode', $legacyWebAuthenticationMode) } Invoke-OctopusServerCommand $args if ($octopusServiceCredential) { $args += @("--username", $octopusServiceCredential.UserName "--password", $octopusServiceCredential.Password | ConvertFrom-SecureString) Update-InstallState "OctopusServiceUsername" $octopusServiceCredential.UserName Update-InstallState "OctopusServicePassword" ($octopusServiceCredential.Password | ConvertFrom-SecureString) Invoke-OctopusServerCommand $args } else { Update-InstallState "OctopusServiceUsername" $null Update-InstallState "OctopusServicePassword" $null } if ($currentState['LicenseKey'] -ne $licenseKey) { $args = @( 'license', '--console', '--instance', $name ) if (($null -eq $licenseKey) -or ($licenseKey -eq "")) { Write-Log "Configuring Octopus Deploy instance to use free license ..." $args += @('--free') } else { Write-Log "Configuring Octopus Deploy instance to use supplied license ..." $args += @('--licenseBase64', $licenseKey) } Invoke-OctopusServerCommand $args } } function Test-ReconfigurationRequired($currentState, $desiredState) { #todo: add admin creds and service creds (need to compare as PSCredential) $reconfigurableProperties = @('ListenPort', 'WebListenPrefix', 'ForceSSL', 'AllowCollectionOfAnonymousUsageStatistics', 'AllowUpgradeCheck', 'LegacyWebAuthenticationMode', 'Home', 'LicenseKey') foreach ($property in $reconfigurableProperties) { if ($currentState.Item($property) -ne ($desiredState.Item($property))) { return $true } } return $false } function Uninstall-OctopusDeploy($name) { $services = @(Get-CimInstance win32_service | Where-Object {$_.PathName -like "`"$($env:ProgramFiles)\Octopus Deploy\Octopus\Octopus.Server.exe*"}) Write-Verbose "Found $($services.length) instance(s)" Write-Log "Uninstalling Octopus Deploy service ..." $args = @( 'service', '--stop', '--uninstall', '--console', '--instance', $name ) Invoke-OctopusServerCommand $args Write-Log "Deleting Octopus Deploy instance ..." $args = @( 'delete-instance', '--console', '--instance', $name ) Invoke-OctopusServerCommand $args if ($services.length -eq 1) { # Uninstall msi Write-Verbose "Uninstalling Octopus..." if (-not (Test-Path "$($env:SystemDrive)\Octopus\logs")) { New-Item -type Directory "$($env:SystemDrive)\Octopus\logs" | out-null } $msiPath = "$($env:SystemDrive)\Octopus\Octopus-x64.msi" $msiLog = "$($env:SystemDrive)\Octopus\logs\Octopus-x64.msi.uninstall.log" if (Test-Path $msiPath) { $msiExitCode = (Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $msiPath /quiet /l*v $msiLog" -Wait -Passthru).ExitCode Write-Verbose "MSI uninstaller returned exit code $msiExitCode" if ($msiExitCode -ne 0) { throw "Removal of Octopus Server failed, MSIEXEC exited with code: $msiExitCode. View the log at $msiLog" } } else { throw "Octopus Server cannot be removed, because the MSI could not be found." } } else { Write-Verbose "Skipping uninstall, as other instances still exist:" foreach ($otherService in $otherServices) { Write-Verbose " - $($otherService.Name)" } } } function Update-OctopusDeploy($name, $downloadUrl, $state, $webListenPrefix) { Write-Verbose "Upgrading Octopus Deploy..." Stop-OctopusDeployService -name $name Install-MSI $downloadUrl if ($state -eq "Started") { Start-OctopusDeployService -name $name -webListenPrefix $webListenPrefix } Write-Verbose "Octopus Deploy upgraded!" } function Start-OctopusDeployService($name, $webListenPrefix) { Write-Log "Starting Octopus Deploy instance ..." $args = @( 'service', '--start', '--console', '--instance', $name ) Invoke-OctopusServerCommand $args # split on semi colons for backwards compat $url = ($webListenPrefix -split ';')[0] # but also split on commas, as Octopus supports both $url = ($url -split ',')[0] $timeout = new-timespan -Minutes 5 $sw = [diagnostics.stopwatch]::StartNew() while (($sw.elapsed -lt $timeout) -and (-not (Test-OctopusDeployServerResponding $url))) { Write-Verbose "$(date) Waiting until server completes startup" Start-Sleep -Seconds 5 } if (-not (Test-OctopusDeployServerResponding $url)) { throw "Server did not come online at $url after $($timeout.TotalMinutes) minutes" } } function Test-OctopusDeployServerResponding($url) { try { Write-Verbose "Checking if $url/api is responding..." Invoke-WebRequest "$url/api" -UseBasicParsing | Out-Null Write-Verbose "Got a successful response from $url/api" return $true } catch { write-verbose "Server returned error $($_)" return $false } } function Stop-OctopusDeployService($name) { Write-Log "Stopping Octopus Deploy instance ..." $args = @( 'service', '--stop', '--console', '--instance', $name ) Invoke-OctopusServerCommand $args } function Get-ServiceName { param ( [string]$instanceName ) if ($instanceName -eq "OctopusServer") { return "OctopusDeploy" } else { return "OctopusDeploy: $instanceName" } } function Install-MSI { param ( [string]$downloadUrl ) Write-Verbose "Beginning installation" mkdir "$($env:SystemDrive)\Octopus" -ErrorAction SilentlyContinue $msiPath = "$($env:SystemDrive)\Octopus\Octopus-x64.msi" if ((Test-Path $msiPath) -eq $true) { Remove-Item $msiPath -force } Request-File $downloadUrl $msiPath Write-Verbose "Installing MSI..." if (-not (Test-Path "$($env:SystemDrive)\Octopus\logs")) { New-Item -type Directory "$($env:SystemDrive)\Octopus\logs" } $msiLog = "$($env:SystemDrive)\Octopus\logs\Octopus-x64.msi.log" $msiExitCode = (Start-Process -FilePath "msiexec.exe" -ArgumentList "/i $msiPath /quiet /l*v $msiLog" -Wait -Passthru).ExitCode Write-Verbose "MSI installer returned exit code $msiExitCode" if ($msiExitCode -ne 0) { throw "Installation of the MSI failed; MSIEXEC exited with code: $msiExitCode. View the log at $msiLog" } Update-InstallState "DownloadUrl" $downloadUrl } function Update-InstallState { param ( [string]$key, [string]$value ) $currentInstallState = @{} if (Test-Path $installStateFile) { $fileContent = (Get-Content -Raw -Path $installStateFile | ConvertFrom-Json) $fileContent.psobject.properties | ForEach-Object { $currentInstallState[$_.Name] = $_.Value } } $currentInstallState.Set_Item($key, $value) $currentInstallState | ConvertTo-Json | set-content $installStateFile } function Request-File { param ( [string]$url, [string]$saveAs ) $retry = $true $retryCount = 0 $maxRetries = 5 while ($retry) { Write-Verbose "Downloading $url to $saveAs" [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12, [System.Net.SecurityProtocolType]::Tls11, [System.Net.SecurityProtocolType]::Tls $downloader = new-object System.Net.WebClient try { $downloader.DownloadFile($url, $saveAs) $retry = $false } catch { Write-Verbose "Failed to download $url" Write-Verbose "Got exception '$($_.Exception.InnerException.Message)'." Write-Verbose "Retrying up to $maxRetries times." if (($_.Exception.InnerException.Message -notlike "The request was aborted: Could not create SSL/TLS secure channel.") -or ($retryCount -gt $maxRetries)) { throw $_.Exception.InnerException } $retryCount = $retryCount + 1 Start-Sleep -Seconds 1 } } } function Write-Log { param ( [string] $message ) $timestamp = ([System.DateTime]::UTCNow).ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss") Write-Verbose "[$timestamp] $message" } function Invoke-AndAssert { param ($block) & $block | Write-Verbose if ($LASTEXITCODE -ne 0 -and $null -ne $LASTEXITCODE) { throw "Command returned exit code $LASTEXITCODE" } } function Get-RegistryValue { param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()]$Path, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()]$Value ) try { return Get-ItemProperty -Path $Path | Select-Object -ExpandProperty $Value -ErrorAction Stop } catch { return "" } } function Install-OctopusDeploy { param ( [Parameter(Mandatory = $True)] [string]$name, [Parameter(Mandatory = $True)] [string]$downloadUrl, [Parameter(Mandatory = $True)] [string]$webListenPrefix, [Parameter(Mandatory = $True)] [string]$sqlDbConnectionString, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential]$octopusAdminCredential, [bool]$allowUpgradeCheck = $true, [bool]$allowCollectionOfAnonymousUsageStatistics = $true, [ValidateSet("UsernamePassword", "Domain", "Ignore")] [string]$legacyWebAuthenticationMode = 'Ignore', [bool]$forceSSL = $false, [int]$listenPort = 10943, [bool]$autoLoginEnabled = $false, [string]$homeDirectory = $null, [PSCredential]$octopusServiceCredential, [string]$licenseKey = $null ) Write-Verbose "Installing Octopus Deploy..." Write-Log "Checking to make sure .net 4.5.1+ is installed" $dotnetVersion = Get-RegistryValue "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" "Release" if (($dotnetVersion -eq "") -or ([int]$dotnetVersion -lt 378675)) { throw "Octopus Server requires .NET 4.5.1. Please install it before attempting to install Octopus Server." } $extractedUserName = $octopusAdminCredential.GetNetworkCredential().UserName $extractedPassword = $octopusAdminCredential.GetNetworkCredential().Password # check if we're upgrading an existing instance $isUpgrade = (Test-Path -LiteralPath $OctopusServerExePath) Write-Log "Setting up new instance of Octopus Deploy with name '$name'" Install-MSI $downloadUrl Write-Log "Creating Octopus Deploy instance ..." $args = @( 'create-instance', '--console', '--instance', $name, '--config', "$($env:SystemDrive)\Octopus\OctopusServer.config" ) if (Test-OctopusVersionSupportsHomeDirectoryDuringCreateInstance) { if (($homeDirectory -ne "") -and ($null -ne $homeDirectory)) { $args += @('--home', $homeDirectory) } else { $args += @('--home', "$($env:SystemDrive)\Octopus") } } Invoke-OctopusServerCommand $args Write-Log "Configuring Octopus Deploy instance ..." if ($OctopusServiceCredential) { $databaseusername = $OctopusServiceCredential.UserName } else { $databaseusername = "NT AUTHORITY\SYSTEM" } $args = @( 'configure', '--console', '--instance', $name, '--upgradeCheck', $allowUpgradeCheck, '--upgradeCheckWithStatistics', $allowCollectionOfAnonymousUsageStatistics, '--webForceSSL', $forceSSL, '--webListenPrefixes', $webListenPrefix, '--commsListenPort', $listenPort ) if (Test-OctopusVersionRequiresDatabaseBeforeConfigure) { if ($isUpgrade -eq $true) { Write-Log "Upgrading Octopus Deploy database for v4" $action = '--upgrade' } else { Write-Log "Creating Octopus Deploy database for v4" $action = '--create' } $dbargs = @( 'database', '--instance', $name, '--connectionstring', $sqlDbConnectionString, $action, '--grant', $databaseusername ) Invoke-OctopusServerCommand $dbargs } else { $args += @("--StorageConnectionString", $sqlDbConnectionString) } if (-not (Test-OctopusVersionSupportsHomeDirectoryDuringCreateInstance)) { if (($homeDirectory -ne "") -and ($null -ne $homeDirectory)) { $args += @('--home', $homeDirectory) } else { $args += @('--home', "$($env:SystemDrive)\Octopus") } } if (Test-OctopusVersionSupportsAutoLoginEnabled) { $args += @('--autoLoginEnabled', $autoLoginEnabled) } elseif ($autoLoginEnabled) { throw "AutoLoginEnabled is only supported from Octopus 3.5.0." } if (Test-OctopusVersionSupportsShowConfiguration) { if ($legacyWebAuthenticationMode -ne 'Ignore') { #todo: add note to use new auth resources throw "LegacyWebAuthenticationMode is only supported for Octopus versions older than 3.5.0." } } else { if ($legacyWebAuthenticationMode -eq 'Ignore') { throw "LegacyWebAuthenticationMode = 'ignore' is only supported from Octopus 3.5.0." } $args += @('--webAuthenticationMode', $legacyWebAuthenticationMode) } Invoke-OctopusServerCommand $args if (-not (Test-OctopusVersionNewerThan (New-Object System.Version 4, 0, 0))) { Write-Log "Creating Octopus Deploy database for v3..." $args = @( 'database', '--console', '--instance', $name, '--create' ) Invoke-OctopusServerCommand $args } Write-Log "Stopping Octopus Deploy instance ..." $args = @( 'service', '--console', '--instance', $name, '--stop' ) Invoke-OctopusServerCommand $args Write-Log "Creating Admin User for Octopus Deploy instance ..." $args = @( 'admin', '--console', '--instance', $name, '--username', $extractedUsername, '--password', $extractedPassword ) Invoke-OctopusServerCommand $args Update-InstallState "OctopusAdminUsername" $extractedUsername Update-InstallState "OctopusAdminPassword" ($OctopusAdminCredential.Password | ConvertFrom-SecureString) if (($null -eq $licenseKey) -or ($licenseKey -eq "")) { Write-Log "Configuring Octopus Deploy instance to use free license ..." $args = @( 'license', '--console', '--instance', $name, '--free' ) } else { Write-Log "Configuring Octopus Deploy instance to use supplied license ..." $args = @( 'license', '--console', '--instance', $name, '--licenseBase64', $licenseKey ) } Invoke-OctopusServerCommand $args Write-Log "Install Octopus Deploy service ..." $args = @( 'service', '--console', '--instance', $name, '--install', '--reconfigure', '--stop' ) if ($octopusServiceCredential) { Write-Log "Adding Service identity to installation command" $args += @("--username", $octopusServiceCredential.UserName "--password", $octopusServiceCredential.GetNetworkCredential().Password ) Write-Log "Adding Service identity to installation command" Update-InstallState "OctopusServiceUsername" $octopusServiceCredential.UserName Update-InstallState "OctopusServicePassword" ($octopusServiceCredential.Password | ConvertFrom-SecureString) } else { Update-InstallState "OctopusServiceUsername" $null Update-InstallState "OctopusServicePassword" $null } Invoke-OctopusServerCommand $args Write-Verbose "Octopus Deploy installed!" } Function Invoke-OctopusServerCommand ($arguments) { $exe = "$($env:ProgramFiles)\Octopus Deploy\Octopus\Octopus.Server.exe" Write-Log "Executing command '$exe $($arguments -join ' ')'" $output = .$exe $arguments Write-CommandOutput $output if (($null -ne $LASTEXITCODE) -and ($LASTEXITCODE -ne 0)) { Write-Error "Command returned exit code $LASTEXITCODE. Aborting." exit 1 } Write-Log "done." } function Write-CommandOutput { param ( [string] $output ) if ($output -eq "") { return } Write-Verbose "" #this isn't quite working foreach ($line in $output.Trim().Split("`n")) { Write-Verbose $line } Write-Verbose "" } function Test-TargetResource { param ( [ValidateSet("Present", "Absent")] [string]$Ensure = "Present", [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Name, [ValidateSet("Started", "Stopped")] [string]$State = "Started", [ValidateNotNullOrEmpty()] [string]$DownloadUrl = "https://octopus.com/downloads/latest/WindowsX64/OctopusServer", [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$WebListenPrefix, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$SqlDbConnectionString, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential]$OctopusAdminCredential, [bool]$AllowUpgradeCheck = $true, [bool]$AllowCollectionOfAnonymousUsageStatistics = $true, [ValidateSet("UsernamePassword", "Domain", "Ignore")] [string]$LegacyWebAuthenticationMode = 'Ignore', [bool]$ForceSSL = $false, [int]$ListenPort = 10943, [bool]$AutoLoginEnabled = $false, [PSCredential]$OctopusServiceCredential, [string]$HomeDirectory, [string]$LicenseKey = $null ) $currentResource = (Get-TargetResource -Ensure $Ensure ` -Name $Name ` -State $State ` -DownloadUrl $DownloadUrl ` -WebListenPrefix $WebListenPrefix ` -SqlDbConnectionString $SqlDbConnectionString ` -OctopusAdminCredential $OctopusAdminCredential ` -AllowUpgradeCheck $AllowUpgradeCheck ` -AllowCollectionOfAnonymousUsageStatistics $AllowCollectionOfAnonymousUsageStatistics ` -LegacyWebAuthenticationMode $LegacyWebAuthenticationMode ` -ForceSSL $ForceSSL ` -ListenPort $ListenPort ` -AutoLoginEnabled $AutoLoginEnabled ` -OctopusServiceCredential $OctopusServiceCredential ` -HomeDirectory $HomeDirectory ` -LicenseKey $LicenseKey) $params = Get-ODSCParameter $MyInvocation.MyCommand.Parameters $currentConfigurationMatchesRequestedConfiguration = $true foreach ($key in $currentResource.Keys) { $currentValue = $currentResource.Item($key) $requestedValue = $params.Item($key) if ($key -eq "OctopusAdminCredential") { if ($null -ne $currentValue) { $currentUsername = $currentValue.GetNetworkCredential().UserName $currentPassword = $currentValue.GetNetworkCredential().Password } else { $currentUserName = "" $currentPassword = "" } $requestedUsername = $requestedValue.GetNetworkCredential().UserName $requestedPassword = $requestedValue.GetNetworkCredential().Password if ($currentPassword -ne $requestedPassword -or $currentUsername -ne $requestedUserName) { Write-Verbose "(FOUND MISMATCH) Configuration parameter '$key' with value '********' mismatched the specified value '********'" $currentConfigurationMatchesRequestedConfiguration = $false } else { Write-Verbose "Configuration parameter '$key' matches the requested value '********'" } } elseif ($currentValue -ne $requestedValue) { Write-Verbose "(FOUND MISMATCH) Configuration parameter '$key' with value '$currentValue' mismatched the specified value '$requestedValue'" $currentConfigurationMatchesRequestedConfiguration = $false } else { Write-Verbose "Configuration parameter '$key' matches the requested value '$requestedValue'" } } return $currentConfigurationMatchesRequestedConfiguration } function Get-ODSCParameter($parameters) { # unfortunately $PSBoundParameters doesn't contain parameters that weren't supplied (because the default value was okay) # credit to https://www.briantist.com/how-to/splatting-psboundparameters-default-values-optional-parameters/ $params = @{} foreach ($h in $parameters.GetEnumerator()) { $key = $h.Key $var = Get-Variable -Name $key -ErrorAction SilentlyContinue if ($null -ne $var) { $val = Get-Variable -Name $key -ErrorAction Stop | Select-Object -ExpandProperty Value -ErrorAction Stop $params[$key] = $val } } return $params } |