HockeyApp - Upload Mobile App

Octopus.Script exported 2015-12-01 by bobjwalker belongs to ‘HockeyApp’ category.

This script uploads a new version of an existing app package to the HockeyApp services.

Parameters

When steps based on the template are included in a project’s deployment process, the parameters below can be set.

HockeyApp Api Token

HockeyAppApiToken

HockeyApp requires an access token for their API as show in the HockeyApp API Authentication Documentation. Logged in users can generate tokens under API Tokens in the account menu.

You should generate an application specific token for this purpose.

HockeyApp App ID

HockeyAppAppID

The ID of your App in HockeyApp. This is visible on the Manage Apps management page for your target app.

Package Step Name

MobileAppPackageStepName

Name of the previously-deployed package step that contains the App that you want to deploy.

Package File Name

PackageFileName

The value is optional.

If no value is provided the scrip will search for exactly one *.apk, *.ipa, or *.appx file in the nupgk package files. Zero or multiple matches will result in an error.

If a value is provided, it will be used directly instead of searching. Use this to specify a mobile app to upload in the case of multiple apps, such as a signed and unsigned apk, or an apk and ipa in a single Nuget package.

This value must be the path and filename relative to the root of the nupkg file. You may use octopus parameters if needed, but the passed value must be the combined relative path with full filename for the package.

For creating the nupkg, the following *.nuspec example will package ALL matching Ipa, Apk (signed), and Appx files. If you have a single package as build output, the Nuget package should work by default.

<?xml version="1.0"?>
<package>
  <metadata>
	<id>$id$</id>
	<title>$id$</title>
	<version>$version$</version>
	<description>Mobile project packaged for Octopus deploy. $description$</description>
  </metadata>
  <files>
	<!-- Matches mobile package files. Note this will only include the platform being built,
		 and should match only a single file. -->

	<!-- Android, Signed only -->
	<file src="**/Release/**/*-Signed.apk" target="" />

	<!-- iOS -->
	<file src="**/Release/**/*.ipa" target="" />

	<!-- Windows App -->
	<file src="**/*.appx" exclude="**/Dependencies/**" target="" />
  </files>
</package>

HockeyApp Notes

HockeyAppNotes =

optional, release notes as Textile or Markdown

As an example, you could use the Octopus variables from the Nuget package extract step as like this:

Deployed from Nuget Package #{Octopus.Action[Nuget Package Extract].Package.NuGetPackageId}, Version #{Octopus.Action[Nuget Package Extract].Package.NuGetPackageVersion}

And get Hockey App notes like:

Deployed from Nuget Package MyiOSPackage, Version 1.2.3.4

See Upload API Documentation

HockeyApp Notes Type

HockeyAppNotesType =

optional, type of release notes:

  • 0 - Textile
  • 1 - Markdown

See Upload API Documentation

HockeyApp Notify

HockeyAppNotify = 1

optional, notify testers (can only be set with full-access tokens):

  • 0 - Don’t notify testers
  • 1 - Notify all testers that can install this app
  • 2 - Notify all testers

See Upload API Documentation

HockeyApp Status

HockeyAppStatus = 2

optional, download status (can only be set with full-access tokens):

  • 1: Don’t allow users to download or install the version
  • 2: Available for download or installation

See Upload API Documentation

HockeyApp Tags

HockeyAppTags

optional, restrict download to comma-separated list of tags

See Upload API Documentation

HockeyApp Teams

HockeyAppTeams

optional, restrict download to comma-separated list of team IDs; example:

teams=12,23,42 with 12, 23, and 42 being the database IDs of your teams

See Upload API Documentation

HockeyApp Users

HockeyAppUsers

optional, restrict download to comma-separated list of user IDs; example:

1224,5678

with 1224 and 5678 being the database IDs of your users

See Upload API Documentation

HockeyApp Mandatory

HockeyAppMandatory

optional, set version as mandatory:

  • 0 - no
  • 1 - yes

See Upload API Documentation

HockeyApp Commit Sha

HockeyAppCommitSha

optional, set to the git commit sha for this build

See Upload API Documentation

HockeyApp Build Server Url

HockeyAppBuildServerUrl

optional, set to the URL of the build job on your build server

See Upload API Documentation

HockeyApp Repository Url

HockeyAppRepositoryUrl

optional, set to your source repository

See Upload API Documentation

Script body

Steps based on this template will execute the following PowerShell script.

# Hockey App Upload script
#
# Uploads a mobile platform application package to Hockey App from a Nuget file
#  extracted in a previous Octopus Deploy step. Allows a variety of parameters.
#
# v0.5 - Turns out Invoke-WebRequest was a memory hog, casuing high memory usage and
#         out-of-memory errors. Switched to dot net native web request and streams.
# v0.4 - Package location search is now recursive, as required by *.nuspec example.
#        Added default description to pass along nuget version to notes.
# v0.3 - Now supports windows .appx packages
# v0.2 - Added extra parameters
# v0.1 - Initial version, basic upload
# 
#
# The following *.nuspec example will package ALL matching Ipa, Apk (signed), and Appx files.
# The upload script requires exactly one match (or specifying the exact file)
# 
# Specify specific package path relative to the nuspec file location (or overriden basepath)
#
# https://docs.nuget.org/create/nuspec-reference#file-element-examples
#
# In some cases the ID, Version, and Description may need manually specified.
#

<#

    <?xml version="1.0"?>
    <package>
      <metadata>
        <id>$id$</id>
        <title>$id$</title>
        <version>$version$</version>
        <description>Mobile project packaged for Octopus deploy. $description$</description>
      </metadata>
      <files>
        <!-- Matches mobile package files. Note this will only include the platform being built,
	         and should match only a single file. -->

        <!-- Android, Signed only -->
        <file src="**/Release/**/*-Signed.apk" target="" />

        <!-- iOS -->
        <file src="**/Release/**/*.ipa" target="" />

        <!-- Windows App -->
        <file src="**/*.appx" exclude="**/Dependencies/**" target="" />
      </files>
    </package>

#>

# Hockey App API reference
#
# General API reference: http://support.hockeyapp.net/kb/api
# Auth reference (tokens): http://support.hockeyapp.net/kb/api/api-basics-and-authentication
# Upload App Version reference: http://support.hockeyapp.net/kb/api/api-versions#upload-version

#############################
# Debug Parameter Overrides #
#############################

# These values are set explicitly durring debugging so that the script can
#   be run in the editor.
# For local debugging, uncomment these values and fill in appropriately.

<#

$OctopusParameters = @{
"HockeyAppApiToken" = "YourApiKeyhere";
"HockeyAppAppID" = "YourAppIdHere";
"PackageFileName" = "MyAppFile-1.2.3.4.ipa"; # app file name
"HockeyAppNotify" = "1";
"HockeyAppStatus" = "2";
}

# debug folder with app files
$stepPath = "C:\Temp\HockeyAppScript\"


# #>

###################################
# Octopus Deploy common functions #
###################################

# A collection of functions that can be used by script steps to determine where packages installed
# by previous steps are located on the filesystem.
 
function Find-InstallLocations {
    $result = @()
    $OctopusParameters.Keys | foreach {
        if ($_.EndsWith('].Output.Package.InstallationDirectoryPath')) {
            $result += $OctopusParameters[$_]
        }
    }
    return $result
}
 
function Find-InstallLocation($stepName) {
    $result = $OctopusParameters.Keys | where {
        $_.Equals("Octopus.Action[$stepName].Output.Package.InstallationDirectoryPath",  [System.StringComparison]::OrdinalIgnoreCase)
    } | select -first 1
 
    if ($result) {
        return $OctopusParameters[$result]
    }
 
    throw "No install location found for step: $stepName"
}

function Find-SingleInstallLocation {
    $all = @(Find-InstallLocations)
    if ($all.Length -eq 1) {
        return $all[0]
    }
    if ($all.Length -eq 0) {
        throw "No package steps found"
    }
    throw "Multiple package steps have run; please specify a single step"
}

#####################
# Utility functions #
#####################

function Get-ExactlyOneMobilePackageFileInfo($searchPath)
{
    $apkFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.apk #Android
    $ipaFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.ipa #iOS
    $appxFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.appx # windows

    $apkCount = $apkFiles.count

    $ipaCount = $ipaFiles.count

    $appxCount = $appxFiles.count

    $totalCount = $apkCount + $ipaCount + $appxCount

    if($totalCount -ne 1)
    {
        throw "Did not find exactly one (1) mobile application package. Found $apkCount APK file(s), $ipaCount IPA file(s), and $appxCount Appx file(s)."
    }

    if($apkCount -eq 1)
    {
        return $apkFiles
    }

    if($ipaCount -eq 1)
    {
        return $ipaFiles
    }

    if($appxCount -eq 1)
    {
        return $appxFiles
    }

    throw "Unable to find mobile application packages (fallback error - not expected)"
}

function AddToHashIfExists([HashTable]$table, $value, $name)
{
    if(-not [String]::IsNullOrWhiteSpace($value))
    {
        $table.Add($name, $value)
    }
}

function GetMultipartFormSectionString($key,$value)
{
    return @"
Content-Disposition: form-data; name="$key"

$value
"@
}

####################
# Basic Parameters #
####################

$apiToken = $OctopusParameters['HockeyAppApiToken']
$appId = $OctopusParameters['HockeyAppAppID']

$octopusFilePathOverride = $OctopusParameters['PackageFileName']

$stepName = $OctopusParameters['MobileAppPackageStepName']

# set step path, if not already set
If([string]::IsNullOrEmpty($stepPath))
{
    if (![string]::IsNullOrEmpty($stepName)) {
        Write-Host "Finding path to package step: $stepName"
        $stepPath = Find-InstallLocation $stepName
    } else {
        $stepPath = Find-SingleInstallLocation
    }
}

Write-Host "Package is located in folder: $stepPath"
Write-Host "##octopus[stderr-progress]"

# if we were not provided a file name, search for a single package file
if([string]::IsNullOrWhiteSpace($octopusFilePathOverride))
{
    $appFileInfo = Get-ExactlyOneMobilePackageFileInfo $stepPath
    $appFullFilePath = $appFileInfo.FullName
}
else
{
    $appFullFilePath = Join-Path $stepPath $octopusFilePathOverride
}

$fileName = [System.IO.Path]::GetFileName($appFullFilePath)

$apiUploadUri = "https://rink.hockeyapp.net/api/2/apps/$appId/app_versions/upload"

# Request token details
$uniqueBoundaryToken = [Guid]::NewGuid().ToString()

$contentType = "multipart/form-data; boundary=$uniqueBoundaryToken"

################################
# Set up Hockey App parameters #
################################

$HockeyAppParameters = @{} # parameters are a hash table.

# add parameters that have values - See docs at http://support.hockeyapp.net/kb/api/api-versions#upload-version

AddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppNotes']          "notes"
AddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppNotesType']      "notes_type"
AddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppNotify']         "notify"
AddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppStatus']         "status"
AddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppTags']           "tags"
AddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppTeams']          "teams"
AddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppUsers']          "users"
AddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppMandatory']      "mandatory"
AddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppCommitSha']      "commit_sha"
AddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppBuildServerUrl'] "build_server_url"
AddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppRepositoryUrl']  "repository_url"

$formSectionSeparator = @"

--$uniqueBoundaryToken

"@

if($HockeyAppParameters.Count -gt 0)
{
    $parameterSectionsString = [String]::Join($formSectionSeparator,($HockeyAppParameters.GetEnumerator() | %{GetMultipartFormSectionString $_.Key $_.Value}))
}

############################
# Prepare request wrappers #
############################

# Standard for multipart form data
# http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4

$stringEncoding = [System.Text.Encoding]::ASCII

# Note the hard-coded "ipa" name here is per HockeyApp API documentation
#  and it applies to ALL platform application files.

$preFileBytes = $stringEncoding.GetBytes(
$parameterSectionsString + 
$formSectionSeparator +
@"
Content-Disposition: form-data; name="ipa"; filename="$fileName"
Content-Type: application/octet-stream


"@)

# file bytes will go in between

$postFileBytes = $stringEncoding.GetBytes(@"

--$uniqueBoundaryToken--
"@)

######################
# Invoke the request #
######################

# Note, previous approach was Invoke-RestMethod based. It worked, but was NOT memory
# efficient, leading to high memory usage and "out of memory" errors.

# Based on examples from
# http://stackoverflow.com/questions/566462/upload-files-with-httpwebrequest-multipart-form-data
# and 
# https://gist.github.com/nolim1t/271018

# Uses a dot net WebRequest and streaming to limit memory usage

$WebRequest = [System.Net.WebRequest]::Create("$apiUploadUri")

$WebRequest.ContentType = $contentType
$WebRequest.Method = "POST"
$WebRequest.KeepAlive = $true;
$WebRequest.Headers.Add("X-HockeyAppToken",$apiToken)

$RequestStream = $WebRequest.GetRequestStream()

# before file bytes
$RequestStream.Write($preFileBytes, 0, $preFileBytes.Length);

#files bytes

$fileMode = [System.IO.FileMode]::Open
$fileAccess = [System.IO.FileAccess]::Read

$fileStream = New-Object IO.FileStream $appFullFilePath,$fileMode,$fileAccess
$bufferSize = 4096 # 4k at a time
$byteBuffer = New-Object Byte[] ($bufferSize)

# read bytes. While bytes are read...
while(($bytesRead = $fileStream.Read($byteBuffer,0,$byteBuffer.Length)) -ne 0)
{
    # write those byes to the request stream
    $RequestStream.Write($byteBuffer, 0, $bytesRead)
}

$fileStream.Close()

# after file bytes
$RequestStream.Write($postFileBytes, 0, $postFileBytes.Length);

$RequestStream.Close()

$response = $WebRequest.GetResponse();

Provided under the Apache License version 2.0.

Report an issue

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

{
  "Id": "5667710e-60b8-4067-bfa5-87196faafdda",
  "Name": "HockeyApp - Upload Mobile App",
  "Description": "This script uploads a new version of an existing app package to the [HockeyApp](http://hockeyapp.net/features/) services.",
  "Version": 20,
  "ExportedAt": "2015-12-01T00:11:50.815+00:00",
  "ActionType": "Octopus.Script",
  "Author": "bobjwalker",
  "Parameters": [
    {
      "Name": "HockeyAppApiToken",
      "Label": "HockeyApp Api Token",
      "HelpText": "HockeyApp requires an access token for their API as show in the [HockeyApp API Authentication Documentation]( http://support.hockeyapp.net/kb/api/api-basics-and-authentication#authentication). Logged in users can generate tokens under [API Tokens](https://rink.hockeyapp.net/manage/auth_tokens) in the account menu.\n\nYou should generate an application specific token for this purpose.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppAppID",
      "Label": "HockeyApp App ID",
      "HelpText": "The ID of your App in HockeyApp. This is visible on the [Manage Apps](https://rink.hockeyapp.net/manage/apps) management page for your target app.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "MobileAppPackageStepName",
      "Label": "Package Step Name",
      "HelpText": "Name of the previously-deployed package step that contains the App that you want to deploy.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "StepName"
      }
    },
    {
      "Name": "PackageFileName",
      "Label": "Package File Name",
      "HelpText": "The value is optional.\n\nIf no value is provided the scrip will search for exactly one *.apk, *.ipa, or *.appx file in the nupgk package files. Zero or multiple matches will result in an error.\n\nIf a value is provided, it will be used directly instead of searching. Use this to specify a mobile app to upload in the case of multiple apps, such as a signed and unsigned apk, or an apk and ipa in a single Nuget package.\n\nThis value must be the path and filename relative to the root of the nupkg file. You may use octopus parameters if needed, but the passed value must be the combined relative path with full filename for the package.\n\nFor creating the nupkg, the following *.nuspec example will package ALL matching Ipa, Apk (signed), and Appx files. If you have a single package as build output, the Nuget package should work by default.\n\n\t<?xml version=\"1.0\"?>\n\t<package>\n\t  <metadata>\n\t\t<id>$id$</id>\n\t\t<title>$id$</title>\n\t\t<version>$version$</version>\n\t\t<description>Mobile project packaged for Octopus deploy. $description$</description>\n\t  </metadata>\n\t  <files>\n\t\t<!-- Matches mobile package files. Note this will only include the platform being built,\n\t\t\t and should match only a single file. -->\n\n\t\t<!-- Android, Signed only -->\n\t\t<file src=\"**/Release/**/*-Signed.apk\" target=\"\" />\n\n\t\t<!-- iOS -->\n\t\t<file src=\"**/Release/**/*.ipa\" target=\"\" />\n\n\t\t<!-- Windows App -->\n\t\t<file src=\"**/*.appx\" exclude=\"**/Dependencies/**\" target=\"\" />\n\t  </files>\n\t</package>",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppNotes",
      "Label": "HockeyApp Notes",
      "HelpText": "optional, release notes as Textile or Markdown\n\nAs an example, you could use the Octopus variables from the Nuget package extract step as like this:\n\nDeployed from Nuget Package #{Octopus.Action[Nuget Package Extract].Package.NuGetPackageId}, Version  #{Octopus.Action[Nuget Package Extract].Package.NuGetPackageVersion}\n\nAnd get Hockey App notes like:\n\nDeployed from Nuget Package MyiOSPackage, Version 1.2.3.4\n\nSee [Upload API Documentation](http://support.hockeyapp.net/kb/api/api-versions#upload-version)",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppNotesType",
      "Label": "HockeyApp Notes Type",
      "HelpText": "optional, type of release notes:\n\n- 0 - Textile\n- 1 - Markdown\n\nSee [Upload API Documentation](http://support.hockeyapp.net/kb/api/api-versions#upload-version)",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppNotify",
      "Label": "HockeyApp Notify",
      "HelpText": "optional, notify testers (can only be set with full-access tokens):\n\n- 0 - Don't notify testers\n- 1 - Notify all testers that can install this app\n- 2 - Notify all testers\n\nSee [Upload API Documentation](http://support.hockeyapp.net/kb/api/api-versions#upload-version)",
      "DefaultValue": "1",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppStatus",
      "Label": "HockeyApp Status",
      "HelpText": "optional, download status (can only be set with full-access tokens):\n\n- 1: Don't allow users to download or install the version\n- 2: Available for download or installation\n\nSee [Upload API Documentation](http://support.hockeyapp.net/kb/api/api-versions#upload-version)",
      "DefaultValue": "2",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppTags",
      "Label": "HockeyApp Tags",
      "HelpText": "optional, restrict download to comma-separated list of tags\n\nSee [Upload API Documentation](http://support.hockeyapp.net/kb/api/api-versions#upload-version)",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppTeams",
      "Label": "HockeyApp Teams",
      "HelpText": "optional, restrict download to comma-separated list of team IDs; example:\n\nteams=12,23,42 with 12, 23, and 42 being the database IDs of your teams\n\nSee [Upload API Documentation](http://support.hockeyapp.net/kb/api/api-versions#upload-version)",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppUsers",
      "Label": "HockeyApp Users",
      "HelpText": "optional, restrict download to comma-separated list of user IDs; example:\n\n    1224,5678\nwith 1224 and 5678 being the database IDs of your users\n\nSee [Upload API Documentation](http://support.hockeyapp.net/kb/api/api-versions#upload-version)",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppMandatory",
      "Label": "HockeyApp Mandatory",
      "HelpText": "optional, set version as mandatory:\n\n- 0 - no\n- 1 - yes\n\nSee [Upload API Documentation](http://support.hockeyapp.net/kb/api/api-versions#upload-version)",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppCommitSha",
      "Label": "HockeyApp Commit Sha",
      "HelpText": "optional, set to the git commit sha for this build\n\nSee [Upload API Documentation](http://support.hockeyapp.net/kb/api/api-versions#upload-version)",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppBuildServerUrl",
      "Label": "HockeyApp Build Server Url",
      "HelpText": "optional, set to the URL of the build job on your build server\n\nSee [Upload API Documentation](http://support.hockeyapp.net/kb/api/api-versions#upload-version)",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "HockeyAppRepositoryUrl",
      "Label": "HockeyApp Repository Url",
      "HelpText": "optional, set to your source repository\n\nSee [Upload API Documentation](http://support.hockeyapp.net/kb/api/api-versions#upload-version)",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.ScriptBody": "# Hockey App Upload script\n#\n# Uploads a mobile platform application package to Hockey App from a Nuget file\n#  extracted in a previous Octopus Deploy step. Allows a variety of parameters.\n#\n# v0.5 - Turns out Invoke-WebRequest was a memory hog, casuing high memory usage and\n#         out-of-memory errors. Switched to dot net native web request and streams.\n# v0.4 - Package location search is now recursive, as required by *.nuspec example.\n#        Added default description to pass along nuget version to notes.\n# v0.3 - Now supports windows .appx packages\n# v0.2 - Added extra parameters\n# v0.1 - Initial version, basic upload\n# \n#\n# The following *.nuspec example will package ALL matching Ipa, Apk (signed), and Appx files.\n# The upload script requires exactly one match (or specifying the exact file)\n# \n# Specify specific package path relative to the nuspec file location (or overriden basepath)\n#\n# https://docs.nuget.org/create/nuspec-reference#file-element-examples\n#\n# In some cases the ID, Version, and Description may need manually specified.\n#\n\n<#\n\n    <?xml version=\"1.0\"?>\n    <package>\n      <metadata>\n        <id>$id$</id>\n        <title>$id$</title>\n        <version>$version$</version>\n        <description>Mobile project packaged for Octopus deploy. $description$</description>\n      </metadata>\n      <files>\n        <!-- Matches mobile package files. Note this will only include the platform being built,\n\t         and should match only a single file. -->\n\n        <!-- Android, Signed only -->\n        <file src=\"**/Release/**/*-Signed.apk\" target=\"\" />\n\n        <!-- iOS -->\n        <file src=\"**/Release/**/*.ipa\" target=\"\" />\n\n        <!-- Windows App -->\n        <file src=\"**/*.appx\" exclude=\"**/Dependencies/**\" target=\"\" />\n      </files>\n    </package>\n\n#>\n\n# Hockey App API reference\n#\n# General API reference: http://support.hockeyapp.net/kb/api\n# Auth reference (tokens): http://support.hockeyapp.net/kb/api/api-basics-and-authentication\n# Upload App Version reference: http://support.hockeyapp.net/kb/api/api-versions#upload-version\n\n#############################\n# Debug Parameter Overrides #\n#############################\n\n# These values are set explicitly durring debugging so that the script can\n#   be run in the editor.\n# For local debugging, uncomment these values and fill in appropriately.\n\n<#\n\n$OctopusParameters = @{\n\"HockeyAppApiToken\" = \"YourApiKeyhere\";\n\"HockeyAppAppID\" = \"YourAppIdHere\";\n\"PackageFileName\" = \"MyAppFile-1.2.3.4.ipa\"; # app file name\n\"HockeyAppNotify\" = \"1\";\n\"HockeyAppStatus\" = \"2\";\n}\n\n# debug folder with app files\n$stepPath = \"C:\\Temp\\HockeyAppScript\\\"\n\n\n# #>\n\n###################################\n# Octopus Deploy common functions #\n###################################\n\n# A collection of functions that can be used by script steps to determine where packages installed\n# by previous steps are located on the filesystem.\n \nfunction Find-InstallLocations {\n    $result = @()\n    $OctopusParameters.Keys | foreach {\n        if ($_.EndsWith('].Output.Package.InstallationDirectoryPath')) {\n            $result += $OctopusParameters[$_]\n        }\n    }\n    return $result\n}\n \nfunction Find-InstallLocation($stepName) {\n    $result = $OctopusParameters.Keys | where {\n        $_.Equals(\"Octopus.Action[$stepName].Output.Package.InstallationDirectoryPath\",  [System.StringComparison]::OrdinalIgnoreCase)\n    } | select -first 1\n \n    if ($result) {\n        return $OctopusParameters[$result]\n    }\n \n    throw \"No install location found for step: $stepName\"\n}\n\nfunction Find-SingleInstallLocation {\n    $all = @(Find-InstallLocations)\n    if ($all.Length -eq 1) {\n        return $all[0]\n    }\n    if ($all.Length -eq 0) {\n        throw \"No package steps found\"\n    }\n    throw \"Multiple package steps have run; please specify a single step\"\n}\n\n#####################\n# Utility functions #\n#####################\n\nfunction Get-ExactlyOneMobilePackageFileInfo($searchPath)\n{\n    $apkFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.apk #Android\n    $ipaFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.ipa #iOS\n    $appxFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.appx # windows\n\n    $apkCount = $apkFiles.count\n\n    $ipaCount = $ipaFiles.count\n\n    $appxCount = $appxFiles.count\n\n    $totalCount = $apkCount + $ipaCount + $appxCount\n\n    if($totalCount -ne 1)\n    {\n        throw \"Did not find exactly one (1) mobile application package. Found $apkCount APK file(s), $ipaCount IPA file(s), and $appxCount Appx file(s).\"\n    }\n\n    if($apkCount -eq 1)\n    {\n        return $apkFiles\n    }\n\n    if($ipaCount -eq 1)\n    {\n        return $ipaFiles\n    }\n\n    if($appxCount -eq 1)\n    {\n        return $appxFiles\n    }\n\n    throw \"Unable to find mobile application packages (fallback error - not expected)\"\n}\n\nfunction AddToHashIfExists([HashTable]$table, $value, $name)\n{\n    if(-not [String]::IsNullOrWhiteSpace($value))\n    {\n        $table.Add($name, $value)\n    }\n}\n\nfunction GetMultipartFormSectionString($key,$value)\n{\n    return @\"\nContent-Disposition: form-data; name=\"$key\"\n\n$value\n\"@\n}\n\n####################\n# Basic Parameters #\n####################\n\n$apiToken = $OctopusParameters['HockeyAppApiToken']\n$appId = $OctopusParameters['HockeyAppAppID']\n\n$octopusFilePathOverride = $OctopusParameters['PackageFileName']\n\n$stepName = $OctopusParameters['MobileAppPackageStepName']\n\n# set step path, if not already set\nIf([string]::IsNullOrEmpty($stepPath))\n{\n    if (![string]::IsNullOrEmpty($stepName)) {\n        Write-Host \"Finding path to package step: $stepName\"\n        $stepPath = Find-InstallLocation $stepName\n    } else {\n        $stepPath = Find-SingleInstallLocation\n    }\n}\n\nWrite-Host \"Package is located in folder: $stepPath\"\nWrite-Host \"##octopus[stderr-progress]\"\n\n# if we were not provided a file name, search for a single package file\nif([string]::IsNullOrWhiteSpace($octopusFilePathOverride))\n{\n    $appFileInfo = Get-ExactlyOneMobilePackageFileInfo $stepPath\n    $appFullFilePath = $appFileInfo.FullName\n}\nelse\n{\n    $appFullFilePath = Join-Path $stepPath $octopusFilePathOverride\n}\n\n$fileName = [System.IO.Path]::GetFileName($appFullFilePath)\n\n$apiUploadUri = \"https://rink.hockeyapp.net/api/2/apps/$appId/app_versions/upload\"\n\n# Request token details\n$uniqueBoundaryToken = [Guid]::NewGuid().ToString()\n\n$contentType = \"multipart/form-data; boundary=$uniqueBoundaryToken\"\n\n################################\n# Set up Hockey App parameters #\n################################\n\n$HockeyAppParameters = @{} # parameters are a hash table.\n\n# add parameters that have values - See docs at http://support.hockeyapp.net/kb/api/api-versions#upload-version\n\nAddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppNotes']          \"notes\"\nAddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppNotesType']      \"notes_type\"\nAddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppNotify']         \"notify\"\nAddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppStatus']         \"status\"\nAddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppTags']           \"tags\"\nAddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppTeams']          \"teams\"\nAddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppUsers']          \"users\"\nAddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppMandatory']      \"mandatory\"\nAddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppCommitSha']      \"commit_sha\"\nAddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppBuildServerUrl'] \"build_server_url\"\nAddToHashIfExists $HockeyAppParameters $OctopusParameters['HockeyAppRepositoryUrl']  \"repository_url\"\n\n$formSectionSeparator = @\"\n\n--$uniqueBoundaryToken\n\n\"@\n\nif($HockeyAppParameters.Count -gt 0)\n{\n    $parameterSectionsString = [String]::Join($formSectionSeparator,($HockeyAppParameters.GetEnumerator() | %{GetMultipartFormSectionString $_.Key $_.Value}))\n}\n\n############################\n# Prepare request wrappers #\n############################\n\n# Standard for multipart form data\n# http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4\n\n$stringEncoding = [System.Text.Encoding]::ASCII\n\n# Note the hard-coded \"ipa\" name here is per HockeyApp API documentation\n#  and it applies to ALL platform application files.\n\n$preFileBytes = $stringEncoding.GetBytes(\n$parameterSectionsString + \n$formSectionSeparator +\n@\"\nContent-Disposition: form-data; name=\"ipa\"; filename=\"$fileName\"\nContent-Type: application/octet-stream\n\n\n\"@)\n\n# file bytes will go in between\n\n$postFileBytes = $stringEncoding.GetBytes(@\"\n\n--$uniqueBoundaryToken--\n\"@)\n\n######################\n# Invoke the request #\n######################\n\n# Note, previous approach was Invoke-RestMethod based. It worked, but was NOT memory\n# efficient, leading to high memory usage and \"out of memory\" errors.\n\n# Based on examples from\n# http://stackoverflow.com/questions/566462/upload-files-with-httpwebrequest-multipart-form-data\n# and \n# https://gist.github.com/nolim1t/271018\n\n# Uses a dot net WebRequest and streaming to limit memory usage\n\n$WebRequest = [System.Net.WebRequest]::Create(\"$apiUploadUri\")\n\n$WebRequest.ContentType = $contentType\n$WebRequest.Method = \"POST\"\n$WebRequest.KeepAlive = $true;\n$WebRequest.Headers.Add(\"X-HockeyAppToken\",$apiToken)\n\n$RequestStream = $WebRequest.GetRequestStream()\n\n# before file bytes\n$RequestStream.Write($preFileBytes, 0, $preFileBytes.Length);\n\n#files bytes\n\n$fileMode = [System.IO.FileMode]::Open\n$fileAccess = [System.IO.FileAccess]::Read\n\n$fileStream = New-Object IO.FileStream $appFullFilePath,$fileMode,$fileAccess\n$bufferSize = 4096 # 4k at a time\n$byteBuffer = New-Object Byte[] ($bufferSize)\n\n# read bytes. While bytes are read...\nwhile(($bytesRead = $fileStream.Read($byteBuffer,0,$byteBuffer.Length)) -ne 0)\n{\n    # write those byes to the request stream\n    $RequestStream.Write($byteBuffer, 0, $bytesRead)\n}\n\n$fileStream.Close()\n\n# after file bytes\n$RequestStream.Write($postFileBytes, 0, $postFileBytes.Length);\n\n$RequestStream.Close()\n\n$response = $WebRequest.GetResponse();\n",
    "Octopus.Action.Script.Syntax": "PowerShell"
  },
  "Category": "HockeyApp",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/hockeyapp-upload-mobile-app.json",
  "Website": "/step-templates/5667710e-60b8-4067-bfa5-87196faafdda",
  "Logo": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAxQTFRF////AZ3kXMDutuP3nz1mRAAAA19JREFUeNrsnNtygzAMRLH2//+5hZAphJuMVzLp7MlbpxOySNbNhmEQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEECILjJ8N05+f/Kt/sYlixcnvv47ARn0PEDD9+nZGSV0EjQpKAKOeNA0xEj7kBIuI17BUE+ZMJR2+o6H0gqmln4q3Fs66KA/AOsiYM905xXLNYu50NiU0tztjmCsY82fUhiiGzPzlSU8IMEdQzrqQY1wdwYn3TIwRdVhCMXScfI21PmxIwjg/4EhHZoFNWfFGDRs3IZgEnf2KdjcfYRDG7fw3QsqTXasQvgIPMAjDtVLD71AiheTZBJQyBZEtTusvwEAxSedaq/ZOIrbvbGkgQPyuWKsYt3vPmWzUjwBBXG1x07OQbtczzSJq8U0ywfdV5owW3hGgRaw6ThtfNdu/bf7KWek83HJuEaF6i6jBjW9NfV+jRhh2eA3k7k1hERjUv2n6i2foQHim/ZJhvDdPfcP+SHf/AiPbdrfKwhoNVcTKOftshq6WaotDLb8KyR62NMF0G9EUrtBn3a9XOJqCl+2utgwtH3EKjVHYjuJf6CkO216tNZ3gJJQHnarZNjhLBwAje+xlJaoY278CJaOYJzMR1Bx1M2AVKnAXC3fai4vjZtugAma9eN4KLs4EOo4XVDaOTZXjZaK6MOmwOpjZ1sKHjMTjJnQI2tJA5ozuLNe2XyzlMMplWkrpEJuOusKTWkkdolUE0zojeL+5U4NoiwNcU7jCO3rdmwYx12LHrp29G2P/Q0avVjd/Z+/hY6D6DSViVR9gCKSb5eOSxlrmlupiHy5lrLViO7E8TsumY+ctettPS/yufafXATN64TiI8MTsljcwbm6382KhVc1RiQZ6OoErtld37adF5moRIqQ8udrA/asNX08lLD4zl48DfPS6xIxyczMa84OVVc9Rbh2VmRSz6oe9sM69UEIxdJCbsgpGRtd+EvcCDG8RXftVqAup4+ELqB5F3hMcUXV8TZr4ezp6/tQ/JBZ45DCztQo+BJolJeEsa4c9xG+VkvksQV5jFS8lQot1ev8DV4v1fYkFR4w94y0jLW5mhoe9KqV2Y7fbG0T8enBYjZhrS1cIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCLHmR4ABAJBvJBFaZlmBAAAAAElFTkSuQmCC",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Tuesday, December 1, 2015