Public/Set-PAServer.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
function Set-PAServer {
    [CmdletBinding()]
    param(
        [Parameter(Position=0,ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [ValidateScript({Test-ValidDirUrl $_ -ThrowOnFail})]
        [Alias('location')]
        [string]$DirectoryUrl,
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateScript({Test-ValidFriendlyName $_ -ThrowOnFail})]
        [string]$Name,
        [ValidateScript({Test-ValidFriendlyName $_ -ThrowOnFail})]
        [string]$NewName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [switch]$SkipCertificateCheck,
        [Parameter(ValueFromPipelineByPropertyName)]
        [switch]$DisableTelemetry,
        [switch]$NoRefresh,
        [switch]$NoSwitch
    )

    Process {

        $curDir = Get-PAServer

        # make sure we have DirectoryUrl, Name, or an existing active server
        if (-not ($DirectoryUrl -or $Name -or $curDir)) {
            try { throw "No DirectoryUrl or Name specified and no active server. Please specify a DirectoryUrl." }
            catch { $PSCmdlet.ThrowTerminatingError($_) }
        }

        # try to find an existing server that matches DirectoryUrl/Name
        if ($DirectoryUrl) {

            # convert WellKnown names to their associated Url
            if ($DirectoryUrl -notlike 'https://*') {
                # save the shortcut to use as the default name
                $shortcutName = $DirectoryUrl
                Write-Debug "$DirectoryUrl converted to $($script:WellKnownDirs.$DirectoryUrl)"
                $DirectoryUrl = $script:WellKnownDirs.$DirectoryUrl
            }

            # ignore the Name parameter when DirectoryUrl is specified
            $newDir = Get-PAServer -DirectoryUrl $DirectoryUrl -Quiet

            # if a cached server doesn't exist, create the basics of a new one
            # that we'll fully populate later
            if (-not $newDir) {

                if (-not $Name) {
                    # generate a default name using the shortcut if it was specified,
                    # otherwise the Host value of the URL
                    $uri = [uri]$DirectoryUrl
                    $Name = if ($shortcutName) {
                        $shortcutName
                    } else {
                        ('{0}_{1}' -f $uri.Host,$uri.Port).Replace('_443','')
                    }
                }

                # make sure another server doesn't exist with this name already
                if (Get-PAServer -Name $Name -Quiet) {
                    try { throw "Another server already exists with Name '$Name'. Please specify a unique value." }
                    catch { $PSCmdlet.ThrowTerminatingError($_) }
                }

                $newDir = [pscustomobject]@{
                    PSTypeName = 'PoshACME.PAServer'
                    location = $DirectoryUrl
                    Name = $Name
                    Folder = Join-Path (Get-ConfigRoot) $Name
                    DisableTelemetry = $DisableTelemetry.IsPresent
                    SkipCertificateCheck = $SkipCertificateCheck.IsPresent
                    newAccount = $null
                    newOrder = $null
                    newNonce = $null
                    keyChange = $null
                    revokeCert = $null
                    meta = $null
                    nonce = $null
                }
            }
        }
        elseif ($Name) {
            # Try to find a server that matches Name instead, but error if one
            # doesn't exist because we don't want to fall back on the current
            # server in case it's not what the user intended to update.
            $newDir = Get-PAServer -Name $Name -Quiet
            if (-not $newDir) {
                try { throw "No PAServer found with Name '$Name'." }
                catch { $PSCmdlet.ThrowTerminatingError($_) }
            }
        }
        else {
            # use the currently active server
            $newDir = $curDir
        }

        # update the cert validation state before we try to refresh
        if ('SkipCertificateCheck' -in $PSBoundParameters.Keys) {
            Set-CertValidation $SkipCertificateCheck.IsPresent
        }

        # refresh the server details if they don't already exist or NoRefresh
        # wasn't specified
        $firstUse = (-not (Test-Path $newDir.Folder -PathType Container))
        if ($firstUse -or -not $NoRefresh) {

            # Warn if they asked not to refresh but there's no cached object
            if ($NoRefresh) {
                Write-Warning "Performing full server update because cached server details are missing."
            }

            # make the request
            Write-Verbose "Updating directory info from $($newDir.location)"
            try {
                $iwrSplat = @{
                    Uri = $newDir.location
                    UserAgent = $script:USER_AGENT
                    ErrorAction = 'Stop'
                    Verbose = $false
                }
                $response = Invoke-WebRequest @iwrSplat @script:UseBasic
            } catch {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            try {
                $dirObj = $response.Content | ConvertFrom-Json
            } catch {
                Write-Debug "ACME Response: `n$($response.Content)"
                try { throw "ACME response from $($newDir.location) was not valid JSON. Details are in Debug output." }
                catch { $PSCmdlet.ThrowTerminatingError($_) }
            }

            # create the server folder if necessary
            if ($firstUse) {
                New-Item -ItemType Directory -Path $newDir.Folder -Force -EA Stop | Out-Null
            }

            # update values from the response object
            $newDir.newAccount = $dirObj.newAccount
            $newDir.newOrder   = $dirObj.newOrder
            $newDir.newNonce   = $dirObj.newNonce
            $newDir.keyChange  = $dirObj.keyChange
            $newDir.revokeCert = $dirObj.revokeCert
            $newDir.meta       = $dirObj.meta

            # update the nonce value
            if ($response.Headers.ContainsKey($script:HEADER_NONCE)) {
                $newDir.nonce = $response.Headers[$script:HEADER_NONCE] | Select-Object -First 1
            } else {
                $newDir.nonce = Get-Nonce $dirObj.newNonce
            }
        }

        # update switch param details if necessary
        if ($newDir.DisableTelemetry -ne $DisableTelemetry.IsPresent) {
            Write-Debug "Setting DisableTelemetry value to $($DisableTelemetry.IsPresent)"
            $newDir | Add-Member 'DisableTelemetry' $DisableTelemetry.IsPresent -Force
        }
        if ($newDir.SkipCertificateCheck -ne $SkipCertificateCheck.IsPresent) {
            Write-Debug "Setting SkipCertificateCheck value to $($SkipCertificateCheck.IsPresent)"
            $newDir | Add-Member 'SkipCertificateCheck' $SkipCertificateCheck.IsPresent -Force
        }

        # save the object to disk except for the dynamic properties
        Write-Debug "Saving PAServer to disk"
        $dirFile = Join-Path $newDir.Folder 'dir.json'
        $newDir | Select-Object -Property * -ExcludeProperty Name,Folder |
            ConvertTo-Json -Depth 5 |
            Out-File $dirFile -Force -EA Stop

        if (-not $NoSwitch) {
            # set as the new active server
            $newDir.location | Out-File (Join-Path (Get-ConfigRoot) 'current-server.txt') -Force -EA Stop
        }

        # Deal with potential name change
        if ($NewName -and $NewName -ne $newDir.Name) {

            # rename the dir folder
            $newFolder = Join-Path (Get-ConfigRoot) $NewName
            if (Test-Path $newFolder) {
                Write-Error "Failed to rename PAServer $($newDir.Name). The path '$newFolder' already exists."
            } else {
                Write-Debug "Renaming $($newDir.Name) server folder to $newFolder"
                Rename-Item $newDir.Folder $newFolder
            }
        }

        # reload config from disk
        Import-PAConfig -Level 'Server'

        # Show a link to the TOS if this is the server's first use.
        if ($firstUse) {
            Write-Host "Please review the Terms of Service here: $($script:Dir.meta.termsOfService)"
        }
    }
}