Functions/Publish-BitbucketDownload.ps1
# Copyright 2012 Aaron Jensen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. function Publish-BitbucketDownload { <# .SYNOPSIS Creates and publishes a ZIP file to Bitbucket so it is available on a project's download page. .DESCRIPTION The `Publish-BitbucketDownload` function creates a ZIP file and publishes it to a repository so it is availabe on a project's download page. If the file already exists on Bitbucket, nothing is uploaded. .LINK https://bitbucket.org/Swyter/bitbucket-curl-upload-to-repo-downloads #> [CmdletBinding()] param( [pscredential] # The Bitbucket credentials to use. $Credential, [Parameter(Mandatory=$true)] [string] # The account of the project you're uploading a file to. $Username, [Parameter(Mandatory=$true)] [string] # The name of the project you're uploading a file to. $ProjectName, [Parameter(Mandatory=$true)] [string[]] # The paths to the files and directories to include in the ZIP file. All files and sub-directories under directory are added. $Path, [Parameter(Mandatory=$true)] [string] # The path to the manifest of the module being published. $ManifestPath ) Set-StrictMode -Version 'Latest' function Assert-Response { param( [Microsoft.PowerShell.Commands.HtmlWebResponseObject] $Response, [Uri] $ExpectedUri ) if( -not $Response ) { Write-Error ('No response.') return $false } if( $Response.BaseResponse.StatusCode -ne [Net.HttpStatusCode]::OK ) { Write-Error ('Response failed.') return $false } if( $Response.BaseResponse.ResponseUri -ne $ExpectedUri ) { Write-Error ('Response didn''t finish on URI {0} ({1}).' -f $ExpectedUri,$Response.BaseResponse.ResponseUri) return $false } $errorElement = $Response.ParsedHtml.getElementById('error') if( $errorElement -and ($errorElement | Get-Member 'innerHtml') -and $erroElement.innerHtml ) { Write-Error $errorElement.innerHtml return $false } return $true } $manifest = Test-ModuleManifest -Path $ManifestPath if( -not $manifest ) { return } $baseProjectUri = 'https://bitbucket.org/{0}/{1}' -f $Username,$ProjectName $zipFileName = "{0}-{1}.zip" -f $manifest.Name,$manifest.Version $zipDownloadUrl = '{0}/downloads/{1}' -f $baseProjectUri,$zipFileName try { $resp = Invoke-WebRequest -Uri $zipDownloadUrl -ErrorAction Ignore $publish = ($resp.StatusCode -ne 200) } catch { $publish = $true } if( -not $publish ) { Write-Warning -Message ('{0} file already published.' -f $zipFileName) return } $zipFilePath = Join-Path -Path $env:TEMP -ChildPath $zipFileName $outFile = '{0}+{1}' -f $manifest.Name,[IO.Path]::GetRandomFileName() $outFile = Join-Path -Path $env:TEMP -ChildPath $outFile try { if( Test-Path $zipFilePath -PathType Leaf ) { Remove-Item $zipFilePath } Write-Verbose -Message ('Creating {0} ZIP file.' -f $zipFileName) Compress-Item -Path $Path -OutFile $zipFilePath $PSDefaultParameterValues.Clear() $PSDefaultParameterValues['Invoke-WebRequest:PassThru'] = $true $PSDefaultParameterValues['Invoke-WebRequest:OutFile'] = $outFile if( -not $Credential ) { $Credential = Get-Credential -Message ('Enter credentials for {0}' -f $baseProjectUri) } $session = $null $loginUri = 'https://bitbucket.org/account/signin/' $resp = Invoke-WebRequest -Uri $loginUri -SessionVariable 'session' -Method Get if( -not (Assert-Response -Response $resp -ExpectedUri $loginUri) ) { return } $PSDefaultParameterValues['Invoke-WebRequest:WebSession'] = $session $form = $resp.Forms | Where-Object { $_.Action -eq '/account/signin/' } $formFields = $form.Fields $formFields.id_username = $Credential.UserName $formFields.id_password = $Credential.GetNetworkCredential().Password $loginUri = 'https://bitbucket.org{0}' -f $form.Action $body = @{ 'username' = $Credential.UserName; 'password' = $Credential.GetNetworkCredential().Password; 'csrfmiddlewaretoken' = $formFields.csrfmiddlewaretoken; 'submit' = ''; 'next' = ''; } $resp = Invoke-WebRequest -Uri $loginUri -Method $form.Method -Body $body -Headers @{ Referer = $loginUri } if( -not (Assert-Response -Response $resp -ExpectedUri 'https://bitbucket.org/') ) { exit 1 } $downloadUri = '{0}/downloads' -f $baseProjectUri $resp = Invoke-WebRequest -Uri $downloadUri -Method Get if( -not (Assert-Response -Response $resp -ExpectedUri $downloadUri) ) { exit 1 } $csrfToken = $resp.Forms | Where-Object { $_.Fields.ContainsKey( 'csrfmiddlewaretoken' ) } | ForEach-Object { $_.Fields.csrfmiddlewaretoken } Write-Debug $csrfToken $boundary = [Guid]::NewGuid().ToString() $bodyStart = @" --$boundary Content-Disposition: form-data; name="csrfmiddlewaretoken" $csrfToken --$boundary Content-Disposition: form-data; name="token" --$boundary Content-Disposition: form-data; name="files"; filename="$(Split-Path -Leaf -Path $zipFilePath)" Content-Type: application/octet-stream "@ $bodyEnd = @" --$boundary-- "@ $requestInFile = Join-Path -Path $env:TEMP -ChildPath ([IO.Path]::GetRandomFileName()) try { $fileStream = New-Object 'System.IO.FileStream' ($requestInFile, [System.IO.FileMode]'Create', [System.IO.FileAccess]'Write') try { $bytes = [Text.Encoding]::UTF8.GetBytes($bodyStart) $fileStream.Write( $bytes, 0, $bytes.Length ) $bytes = [IO.File]::ReadAllBytes($zipFilePath) $fileStream.Write( $bytes, 0, $bytes.Length ) $bytes = [Text.Encoding]::UTF8.GetBytes($bodyEnd) $fileStream.Write( $bytes, 0, $bytes.Length ) } finally { $fileStream.Close() } $contentType = 'multipart/form-data; boundary={0}' -f $boundary $resp = Invoke-WebRequest -Uri $downloadUri ` -Method Post ` -InFile $requestInFile ` -ContentType $contentType ` -Headers @{ Referer = $downloadUri } if( -not (Assert-Response -Response $resp -ExpectedUri $downloadUri) ) { return } } finally { Remove-Item -Path $requestInFile } $numTries = 10 $tryNum = 0 while( $tryNum++ -lt $numTries ) { try { $resp = Invoke-WebRequest -Uri $zipDownloadUrl $resp | Select-Object -Property 'StatusCode','StatusDescription',@{ Name = 'Uri'; Expression = { $zipDownloadUrl }} break } catch { Start-Sleep -Seconds 1 } } } finally { if( (Test-Path -Path $outFile -PathType Leaf) ) { Remove-Item -Path $outFile } if( (Test-Path -Path $zipFilePath -PathType Leaf) ) { Remove-Item -Path $zipFilePath } } } |