diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d0cc95c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "[powershell]": { + "debug.saveBeforeStart": "nonUntitledEditorsInActiveGroup", + "editor.semanticHighlighting.enabled": false, + "editor.wordSeparators": "`~!@#$%^&*()=+[{]}\\|;:'\",.<>/?", + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "modificationsIfAvailable", + }, + "diffEditor.experimental.showMoves": true, + "diffEditor.experimental.useTrueInlineView": true, + "powershell.codeFormatting.autoCorrectAliases": true, + "powershell.codeFormatting.avoidSemicolonsAsLineTerminators": true, + "powershell.codeFormatting.newLineAfterCloseBrace": false, + "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationForFirstPipeline", + "powershell.codeFormatting.preset": "OTBS", + "powershell.codeFormatting.trimWhitespaceAroundPipe": true, + "powershell.codeFormatting.useCorrectCasing": true, + "powershell.codeFormatting.whitespaceAfterSeparator": true, + "powershell.codeFormatting.whitespaceBetweenParameters": false, + "powershell.debugging.executeMode": "DotSource", + "powershell.pester.useLegacyCodeLens": false +} \ No newline at end of file diff --git a/Initialize-C4bSetup.ps1 b/Initialize-C4bSetup.ps1 index cb8541b..64701ce 100644 --- a/Initialize-C4bSetup.ps1 +++ b/Initialize-C4bSetup.ps1 @@ -180,7 +180,7 @@ try { # Collect current certificate configuration $Certificate = Get-Certificate -Thumbprint $Thumbprint Copy-CertToStore -Certificate $Certificate - + $null = Test-CertificateDomain -Thumbprint $Thumbprint } elseif ($PSScriptRoot) { # We're going to be using a self-signed certificate @@ -215,7 +215,7 @@ try { # Set Choco Server Chocolatey Configuration Invoke-Choco feature enable --name="'excludeChocolateyPackagesDuringUpgradeAll'" Invoke-Choco feature enable --name="'usePackageHashValidation'" - + # Convert license to a "choco-license" package, and install it locally to test Write-Host "Creating a 'chocolatey-license' package, and testing install." -ForegroundColor Green Set-Location $FilesDir diff --git a/Start-C4bCcmSetup.ps1 b/Start-C4bCcmSetup.ps1 index 3fb453b..45cd7cd 100644 --- a/Start-C4bCcmSetup.ps1 +++ b/Start-C4bCcmSetup.ps1 @@ -174,7 +174,7 @@ process { $chocoArgs += @("--package-parameters='/CertificateThumbprint=$Thumbprint'") } & Invoke-Choco @chocoArgs - + if (-not $MyCertificate) { $MyCertificate = Get-Item Cert:\LocalMachine\My\* } Write-Host "Installing Chocolatey Central Management Website" @@ -191,17 +191,23 @@ process { New-CcmBinding -Thumbprint $Thumbprint Start-CcmService + # If the user provided certificate has multiple SANs, they may need to select one with params + if (-not (Get-ChocoEnvironmentProperty CertSubject)) { + $CertSubject = (Get-Item Cert:\LocalMachine\TrustedPeople\$Thumbprint).Subject -replace '^CN=' + Set-ChocoEnvironmentProperty CertSubject $CertSubject + } + $CcmEndpoint = "https://$(Get-ChocoEnvironmentProperty CertSubject)" } choco config set centralManagementServiceUrl "$($CcmEndpoint):24020/ChocolateyManagementService" #Generate CCM Salt Values - if (-not (Get-ChocoEnvironmentProperty ClientSalt)) { + if (-not ($ClientSaltValue = Get-ChocoEnvironmentProperty ClientSalt)) { $ClientSaltValue = New-ServicePassword Set-ChocoEnvironmentProperty ClientSalt $ClientSaltValue } - if (-not (Get-ChocoEnvironmentProperty ServiceSalt)) { + if (-not ($ServiceSaltValue = Get-ChocoEnvironmentProperty ServiceSalt)) { $ServiceSaltValue = New-ServicePassword Set-ChocoEnvironmentProperty ServiceSalt $ServiceSaltValue } diff --git a/Start-C4bNexusSetup.ps1 b/Start-C4bNexusSetup.ps1 index 910a23f..9dc4c23 100644 --- a/Start-C4bNexusSetup.ps1 +++ b/Start-C4bNexusSetup.ps1 @@ -15,21 +15,21 @@ C4B Quick-Start Guide Nexus setup script - Setup of firewall rule for repository access #> [CmdletBinding()] -param( +param( # The certificate thumbprint that identifies the target SSL certificate in # the local machine certificate stores. [Parameter()] [ArgumentCompleter({ - Get-ChildItem Cert:\LocalMachine\TrustedPeople | ForEach-Object { - [System.Management.Automation.CompletionResult]::new( - $_.Thumbprint, - $_.Thumbprint, - "ParameterValue", - ($_.Subject -replace "^CN=(?.+),?.*$",'${FQDN}') - ) - } - })] - [ValidateScript({Test-CertificateDomain -Thumbprint $_})] + Get-ChildItem Cert:\LocalMachine\TrustedPeople | ForEach-Object { + [System.Management.Automation.CompletionResult]::new( + $_.Thumbprint, + $_.Thumbprint, + "ParameterValue", + ($_.Subject -replace "^CN=(?.+),?.*$", '${FQDN}') + ) + } + })] + [ValidateScript({ Test-CertificateDomain -Thumbprint $_ })] [string] $Thumbprint = $( if ((Test-Path C:\choco-setup\clixml\chocolatey-for-business.xml) -and (Import-Clixml C:\choco-setup\clixml\chocolatey-for-business.xml).CertThumbprint) { @@ -51,17 +51,17 @@ process { # Install base nexus-repository package Write-Host "Installing Sonatype Nexus Repository" - $chocoArgs = @('install', 'nexus-repository', '-y' ,'--no-progress', "--package-parameters='/Fqdn:localhost'") + $chocoArgs = @('install', 'nexus-repository', '-y' , '--no-progress', "--package-parameters='/Fqdn:localhost'") & Invoke-Choco @chocoArgs - $chocoArgs = @('install', 'nexushell', '-y' ,'--no-progress') + $chocoArgs = @('install', 'nexushell', '-y' , '--no-progress') & Invoke-Choco @chocoArgs if ($Thumbprint) { $NexusPort = 8443 $null = Set-NexusCert -Thumbprint $Thumbprint -Port $NexusPort - + if ($CertificateDnsName = Get-ChocoEnvironmentProperty CertSubject) { # Override the domain, so we don't get prompted for wildcard certificates Get-NexusLocalServiceUri -HostnameOverride $CertificateDnsName | Write-Verbose @@ -71,10 +71,10 @@ process { # Add Nexus port access via firewall $FwRuleParams = @{ DisplayName = "Nexus Repository access on TCP $NexusPort" - Direction = 'Inbound' - LocalPort = $NexusPort - Protocol = 'TCP' - Action = 'Allow' + Direction = 'Inbound' + LocalPort = $NexusPort + Protocol = 'TCP' + Action = 'Allow' } $null = New-NetFirewallRule @FwRuleParams @@ -262,7 +262,7 @@ process { # Remove Local Chocolatey Setup Source $chocoArgs = @('source', 'remove', '--name="LocalChocolateySetup"') & Invoke-Choco @chocoArgs - + # Install a non-IE browser for browsing the Nexus web portal. if (-not (Test-Path 'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe')) { Write-Host "Installing Microsoft Edge, to allow viewing the Nexus site" diff --git a/Start-C4bPsuSetup.ps1 b/Start-C4bPsuSetup.ps1 new file mode 100644 index 0000000..2684c90 --- /dev/null +++ b/Start-C4bPsuSetup.ps1 @@ -0,0 +1,142 @@ +#requires -Modules C4B-Environment +<# +.SYNOPSIS +C4B Quick-Start Guide PowerShell Universal setup script + +.DESCRIPTION +- Performs the following PowerShell Universal setup + - Install of PowerShell Universal package + - Creation of Chocolatey-specific jobs from template files +#> +[CmdletBinding()] +param( + # The certificate thumbprint that identifies the target SSL certificate in + # the local machine certificate stores. + [Parameter()] + [ArgumentCompleter({ + Get-ChildItem Cert:\LocalMachine\TrustedPeople | ForEach-Object { + [System.Management.Automation.CompletionResult]::new( + $_.Thumbprint, + $_.Thumbprint, + "ParameterValue", + ($_.Subject -replace "^CN=(?.+),?.*$", '${FQDN}') + ) + } + })] + [ValidateScript({ Test-CertificateDomain -Thumbprint $_ })] + [string]$Thumbprint = $( + if ((Test-Path C:\choco-setup\clixml\chocolatey-for-business.xml) -and (Import-Clixml C:\choco-setup\clixml\chocolatey-for-business.xml).CertThumbprint) { + (Import-Clixml C:\choco-setup\clixml\chocolatey-for-business.xml).CertThumbprint + } else { + Get-ChildItem Cert:\LocalMachine\TrustedPeople -Recurse | Sort-Object { + $_.Issuer -eq $_.Subject # Prioritise any certificates above self-signed + } | Select-Object -ExpandProperty Thumbprint -First 1 + } + ), + + # Optional: Sets PSU to use a provided SQL database instead of SQLLite. + [string]$ConnectionString +) +try { + $DefaultEap = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + Start-Transcript -Path "$env:SystemDrive\choco-setup\logs\Start-C4bPsuSetup-$(Get-Date -Format 'yyyyMMdd-HHmmss').txt" + + Invoke-Choco upgrade powershelluniversal-remove-default-listener.hook --confirm --no-progress + + # Install PowerShell Universal + Invoke-Choco upgrade powershelluniversal --confirm --no-progress --install-args="STARTSERVICE=0$(if ($ConnectionString) {" CONNECTIONSTRING=$ConnectionString DATABASETYPE=SQL"})" + $PSUPort = 5000 + + # Handle configuration + $ConfigurationFile = Join-Path $env:ProgramData "PowerShellUniversal/appsettings.json" + $CurrentConfiguration = Get-Content $ConfigurationFile | ConvertFrom-Json + + if ($Thumbprint) { + $PSUPort = 5000 + $CurrentConfiguration.Kestrel.Endpoints = @{ + HTTPS = @{ + Url = "https://$(Get-ChocoEnvironmentProperty CertSubject):$PSUPort" + Certificate = @{ + Thumbprint = $Thumbprint + Store = "TrustedPeople" + Location = "LocalMachine" + AllowInvalid = "true" + } + } + } + } + + if ($ConnectionString) { + $CurrentConfiguration.Data.ConnectionString = $ConnectionString + } + + if ((Get-Content $ConfigurationFile -Raw) -ne ($CurrentConfiguration | ConvertTo-Json -Depth 10)) { + $CurrentConfiguration | ConvertTo-Json -Depth 10 | Set-Content $ConfigurationFile + } + + # Future consideration: parameter to disable external access? + $FwRuleParams = @{ + DisplayName = "PowerShellUniversal Access" + Direction = 'Inbound' + LocalPort = $PSUPort + Protocol = 'TCP' + Action = 'Allow' + } + $null = New-NetFirewallRule @FwRuleParams # Set-EnvFirewallRule @FwRuleParams + + # Create admin user + if (-not ($User = Get-ChocoEnvironmentProperty PSUCredential)) { + $User = [pscredential]::new( + "admin", + (New-ServicePassword) + ) + Set-ChocoEnvironmentProperty PSUCredential $User + + [System.Environment]::SetEnvironmentVariable('PSUDefaultAdminName', $User.UserName, [System.EnvironmentVariableTarget]::Machine) + [System.Environment]::SetEnvironmentVariable('PSUDefaultAdminPassword', $User.Password.ToPlainText(), [System.EnvironmentVariableTarget]::Machine) + } + + # Set Security Defaults + $RepositoryDirectory = Join-Path $env:ProgramData UniversalAutomation\Repository + + if (-not (Test-Path $RepositoryDirectory -PathType Container)) { + $null = New-Item -Path $RepositoryDirectory -ItemType Directory + } + + if (-not (Test-Path $RepositoryDirectory\.universal\settings.ps1)) { + $null = New-Item -Path $RepositoryDirectory\.universal\settings.ps1 -Value @' +$Parameters = @{ + EnhancedAppTokenSecurity = $true + ApiSecurityModel = $true +} +Set-PSUSetting @Parameters +'@ -Force + } + + # Deploy jobs and dashboards + Invoke-Choco upgrade chocolatey-licensed-psu-environment --confirm --no-progress + + # Start service + Start-Service PowerShellUniversal + + # Wait until the username and password have been initialized on the first run + if ([System.Environment]::GetEnvironmentVariable('PSUDefaultAdminPassword', [System.EnvironmentVariableTarget]::Machine)) { + Write-Verbose "[$(Get-Date -Format 'hh:mm:ss')] Waiting for PowerShell Universal to start..." + while (-not (Select-String -Path $env:ProgramData\PowerShellUniversal\Logs\System\systemLog$(Get-Date -Format "yyyyMMdd").txt -Pattern "\[INF\]\[UniversalAutomation\.StartupService\] Startup complete.$")) { + Start-Sleep -Seconds 5 + } + Write-Verbose "[$(Get-Date -Format 'hh:mm:ss')] PowerShell Universal has successfully started." + } + + # Save useful params + Update-Clixml -Properties @{ + PowerShellUniversalUri = "https://$(Get-ChocoEnvironmentProperty CertSubject):$PSUPort" + } +} finally { + [System.Environment]::SetEnvironmentVariable('PSUDefaultAdminName', $null, [System.EnvironmentVariableTarget]::Machine) + [System.Environment]::SetEnvironmentVariable('PSUDefaultAdminPassword', $null, [System.EnvironmentVariableTarget]::Machine) + + $ErrorActionPreference = $DefaultEap + Stop-Transcript +} \ No newline at end of file diff --git a/scripts/Create-ChocoLicensePkg.ps1 b/scripts/Create-ChocoLicensePkg.ps1 index 395ecbc..263f017 100644 --- a/scripts/Create-ChocoLicensePkg.ps1 +++ b/scripts/Create-ChocoLicensePkg.ps1 @@ -48,8 +48,8 @@ $licensePackageNuspec = "$licensePackageFolder\$LicensePackageId.nuspec" # Get license expiration date and node count [xml]$licenseXml = Get-Content -Path $LicensePath $licenseExpiration = [datetimeoffset]::Parse("$($licenseXml.SelectSingleNode('/license').expiration) +0") -$null = $licenseXml.license.name -match "(?<=\[).*(?=\])" -$licenseNodeCount = $Matches.Values -replace '\s[A-Za-z]+','' +$null = $licenseXml.license.name -match "(?<=\[)(?.*)(?=\])" +$licenseNodeCount = $Matches['NodeCount'] -replace '\s[A-Za-z]+' if ($licenseExpiration -lt [datetimeoffset]::UtcNow) { Write-Warning "THE LICENSE FILE AT '$LicensePath' is EXPIRED. This is the file used by this script to generate this package, not at '$licensePackageFolder'"