Receive-RemoteItem.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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#Requires -Version 3

function Receive-RemoteItem {
    <#
        .SYNOPSIS
            Downloads a file or media item through a Sitecore PowerShell Extensions web service.
 
       .PARAMETER Path
            The source path of the item to download on the server side.
             
       .PARAMETER Destination
            The destination path of the item on the client side.
             
       .PARAMETER RootPath
            The predefined directory in which the item will be downloaded from. This is simply a keyword that maps to a predefined location on the server side.
             
            When using this you can simply provide the file or media item name in the Path parameter.
             
       .EXAMPLE
            The following downloads an item from the media library in the master db and overwrite any existing version.
     
            $session = New-ScriptSession -Username admin -Password b -ConnectionUri http://remotesitecore
            Receive-RemoteItem -Session $session -Path "/Default Website/cover" -Destination "C:\Images\" -Database master -Force
     
       .EXAMPLE
            The following downloads an item from the media library using the Id in the master db and uses the specified name.
     
            $session = New-ScriptSession -Username admin -Password b -ConnectionUri http://remotesitecore
            Receive-RemoteItem -Session $session -Path "{04DAD0FD-DB66-4070-881F-17264CA257E1}" -Destination "C:\Images\cover1.jpg" -Database master -Force
     
        .EXAMPLE
            The following downloads all the items from the media library in the specified path.
     
            $session = New-ScriptSession -Username admin -Password b -ConnectionUri http://remotesitecore
            Invoke-RemoteScript -Session $session -ScriptBlock {
                Get-ChildItem -Path "master:/sitecore/media library/" -Recurse |
                    Where-Object { $_.Size -gt 0 } | Select-Object -Expand ItemPath
            } | Receive-RemoteItem -Destination "C:\Images\" -Database master
            Stop-ScriptSession -Session $session
 
        .EXAMPLE
            The following downloads a file from the application root path.
 
            $session = New-ScriptSession -Username admin -Password b -ConnectionUri http://remotesitecore
            Receive-RemoteItem -Session $session -Path "default.js" -RootPath App -Destination "C:\Files\"
            Stop-ScriptSession -Session $session
               
        .EXAMPLE
            The following compresses the log files into an archive and downloads from the absolute path.
 
            $session = New-ScriptSession -Username admin -Password b -ConnectionUri http://remotesitecore
            Invoke-RemoteScript -Session $session -ScriptBlock {
                Import-Function -Name Compress-Archive
                Get-ChildItem -Path "$($SitecoreLogFolder)" | Where-Object { !$_.PSIsContainer } |
                    Compress-Archive -DestinationPath "$($SitecoreDataFolder)archived.zip" -Recurse | Select-Object -Expand FullName
            } | Receive-RemoteItem -Session $session -Destination "C:\Files\"
     
        .LINK
            Send-RemoteItem
 
        .LINK
            New-ScriptSession
 
        .LINK
            Stop-ScriptSession
 
 
    #>

    [CmdletBinding(DefaultParameterSetName='Uri and File')]
    param(
        [Parameter(Mandatory=$true, ParameterSetName='Session and File')]
        [Parameter(Mandatory=$true, ParameterSetName='Session and Database')]
        [ValidateNotNull()]
        [pscustomobject]$Session,
        
        [Parameter(Mandatory=$true, ParameterSetName='Uri and File')]
        [Parameter(Mandatory=$true, ParameterSetName='Uri and Database')]
        [Uri[]]$ConnectionUri,

        [Parameter(ParameterSetName='Uri and File')]
        [Parameter(ParameterSetName='Uri and Database')]
        [string]$Username,

        [Parameter(ParameterSetName='Uri and File')]
        [Parameter(ParameterSetName='Uri and Database')]
        [string]$Password,

        [Parameter(ParameterSetName='Uri and File')]
        [Parameter(ParameterSetName='Uri and Database')]
        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(ParameterSetName='Session and File')]
        [Parameter(ParameterSetName='Uri and File')]
        [ValidateSet("App", "Data", "Debug", "Index", "Layout", "Log", "Media", "Package", "Serialization", "Temp")]
        [ValidateNotNullOrEmpty()]
        [string]$RootPath,

        [Parameter(Mandatory=$true, ParameterSetName='Session and Database')]
        [Parameter(Mandatory=$true, ParameterSetName='Uri and Database')]
        [ValidateNotNullOrEmpty()]
        [string]$Database,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Destination,

        [Parameter()]
        [switch]$Force,

        #[Parameter(ParameterSetName='Session and File')]
        [Parameter(ParameterSetName='Session and Database')]
        #[Parameter(ParameterSetName='Uri and File')]
        [Parameter(ParameterSetName='Uri and Database')]
        [switch]$Container
    )

    process {

        $isMediaItem = ![string]::IsNullOrEmpty($Database) -or ($RootPath -eq "media" -and ![System.IO.Path]::HasExtension($Path))
        
        if($isMediaItem -and !$Database){
            $Database = "master"
        }
        
        if(!$isMediaItem -and (!$RootPath -and ![System.IO.Path]::IsPathRooted($Path))) {
            Write-Error -Message "RootPath is required when Path is not fully qualified." -ErrorAction Stop
        }

        $Path = $Path.TrimEnd('\','/')

        if($Session) {
            $Username = $Session.Username
            $Password = $Session.Password
            $SharedSecret = $Session.SharedSecret
            $Credential = $Session.Credential
            $UseDefaultCredentials = $Session.UseDefaultCredentials
            $ConnectionUri = $Session | ForEach-Object { $_.Connection.BaseUri }
        }

        $itemType = "undetermined"

        $serviceUrl = "/-/script"

        if($isMediaItem) {
            $serviceUrl += "/media/" + $Database + "/" + $Path + "/?"
            $itemType = "media"
        } else {
            $serviceUrl += "/file/" + $RootPath + "/?path=" + $Path + "&"
            $itemType = "file"
        }

        foreach($uri in $ConnectionUri) {
            
            # http://hostname/-/script/type/origin/location
            $url = $uri.AbsoluteUri.TrimEnd("/") + $serviceUrl

            Write-Verbose -Message "Preparing to download remote item from the url $($url)"
            Write-Verbose -Message "Downloading the $($itemType) item $($Path)"
            Add-Type -AssemblyName System.Net.Http
            $handler = New-Object System.Net.Http.HttpClientHandler
            $handler.AutomaticDecompression = [System.Net.DecompressionMethods]::GZip -bor [System.Net.DecompressionMethods]::Deflate
            $client = New-Object -TypeName System.Net.Http.Httpclient $handler

            if(![string]::IsNullOrEmpty($SharedSecret)) {
                $token = New-Jwt -Algorithm 'HS256' -Issuer 'SPE Remoting' -Audience ($uri.GetLeftPart([System.UriPartial]::Authority)) -Name $Username -SecretKey $SharedSecret -ValidforSeconds 30
                $client.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", $token)
            } else {
                $authBytes = [System.Text.Encoding]::GetEncoding("iso-8859-1").GetBytes("$($Username):$($Password)")
                $client.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue("Basic", [System.Convert]::ToBase64String($authBytes))
            }
                      
            if($Credential) {
                $handler.Credentials = $Credential
            }

            if($UseDefaultCredentials) {
                $handler.UseDefaultCredentials = $UseDefaultCredentials
            }

            [System.Net.HttpWebResponse]$script:errorResponse = $null
            [System.Net.Http.HttpResponseMessage]$responseMessage = $null
           
            try {
                $script:errorResponse = $null
                $script:ex = $null

                $responseMessage = $client.GetAsync($url).Result
                [byte[]]$response = $responseMessage.Content.ReadAsByteArrayAsync().Result                   
            } catch [System.Net.WebException] {
                [System.Net.WebException]$script:ex = $_.Exception
                [System.Net.HttpWebResponse]$script:errorResponse = $script:ex.Response
                Write-Verbose -Message "Response exception message: $($ex.Message)"
                Write-Verbose -Message "Response status description: $($errorResponse.StatusDescription)"
                if($errorResponse.StatusCode -eq [System.Net.HttpStatusCode]::Forbidden) {
                    Write-Verbose -Message "Check that the proper credentials are provided and that the service configurations are enabled."
                } elseif ($errorResponse.StatusCode -eq [System.Net.HttpStatusCode]::NotFound){
                    Write-Verbose -Message "Check that the service files exist and are properly configured."
                }
            }

            if($errorResponse){
                Write-Error -Message "Server response: $($errorResponse.StatusDescription)" -Category ConnectionError `
                    -CategoryActivity "Download" -CategoryTargetName $uri -Exception ($script:ex) -CategoryReason "$($errorResponse.StatusCode)" -CategoryTargetType $RootPath 
            }

            if($response -and $response.Length -gt 0 -or $responseMessage.Content.Headers.Count -gt 0) {
                if(!$responseMessage.IsSuccessStatusCode) {
                    Write-Error "Download failed. $($responseMessage.ReasonPhrase)"
                    return    
                }
                $contentType = $responseMessage.Content.Headers.GetValues("Content-Type")[0]
                $contentDisposition = $responseMessage.Content.Headers.GetValues("Content-Disposition")[0]
                $filename = ""
                Write-Verbose -Message "Response content length: $($response.Length) bytes"
                Write-Verbose -Message "Response content type: $($contentType)"
                
                if($contentDisposition.IndexOf("filename=") -gt -1) {
                    $filename = $contentDisposition.Substring($contentDisposition.IndexOf("filename=") + 10).Replace('"', "")
                    Write-Verbose -Message "Response filename: $($filename)"
                }
     
                $directory = [System.IO.Path]::GetDirectoryName($Destination)
                if(!$directory) {
                    $directory = $Destination
                }
                
                if(!(Test-Path $directory -PathType Container)) {
                    Write-Verbose "Creating a new directory $($directory)"
                    New-Item -ItemType Directory -Path $directory | Out-Null
                }

                $output = $Destination
                if($Container) {
                    # Preserve Media Item directory structure
                    if($isMediaItem) {
                        $output = Join-Path -Path $output -ChildPath $Path.Replace('/','\').Replace("\sitecore\media library\", "")
                    }
                }

                $extension = [System.IO.Path]::GetExtension($output)
                if(!$extension) {
                    $extension = [System.IO.Path]::GetExtension($filename)

                    if(!$extension) {
                        Write-Error -Message "The file extension could not be determined."
                    }
                    
                    # If a media item is requested it will use the filename as the last part.
                    $filenameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($Path)
                    if($output.EndsWith($filenameWithoutExtension)) {
                        $output = $output.Substring(0, $output.Length - $filenameWithoutExtension.Length)        
                    }
                    $output = Join-Path -Path $output -ChildPath $filename
                    
                } else {
                    Write-Verbose "Overriding the filename $($filename) with $([System.IO.Path]::GetFileName($output))"
                }
                
                if(-not(Test-Path $output -PathType Leaf) -or $Force.IsPresent) {
                    Write-Verbose "Creating a new file $($output)"
                    New-Item -Path $output -ItemType File -Force | Out-Null
                    if($response) {
                        [System.IO.File]::WriteAllBytes((Convert-Path -Path $output), $response)
                    }
                } else {
                    Write-Verbose "Skipping the save of $($output) because it already exists."
                }

                Write-Verbose "Download complete."
            } else {
                Write-Verbose -Message "Download failed. No content returned from the web service."
            }
        }
    }
}