├── .gitignore ├── Modules ├── PowerDelivery │ ├── Cmdlets │ │ ├── Enter-DeliveryRole.ps1 │ │ ├── Invoke-MSBuild.ps1 │ │ ├── New-DeliveryCredential.ps1 │ │ ├── New-DeliveryKey.ps1 │ │ ├── New-DeliveryProject.ps1 │ │ ├── New-DeliveryRole.ps1 │ │ ├── New-DeliverySecret.ps1 │ │ ├── Publish-DeliveryFilesToAzure.ps1 │ │ ├── Publish-DeliveryFilesToS3.ps1 │ │ ├── Start-Delivery.ps1 │ │ └── Write-RelativePath.ps1 │ ├── PowerDelivery.psd1 │ ├── PowerDelivery.psm1 │ ├── TODO.txt │ └── Templates │ │ ├── Configuration.ps1.template │ │ ├── Environment.ps1.template │ │ ├── Role.ps1.template │ │ ├── Target.ps1.template │ │ └── _Shared.ps1.template └── PowerDeliveryNode │ ├── Cmdlets │ ├── Get-DeliveryFilesFromAzure.ps1 │ ├── Get-DeliveryFilesFromS3.ps1 │ ├── New-DeliveryReleasePath.ps1 │ ├── Test-CommandExists.ps1 │ ├── Test-Verbose.ps1 │ └── Undo-DeliveryReleasePath.ps1 │ ├── PowerDeliveryNode.psd1 │ ├── PowerDeliveryNode.psm1 │ └── Templates │ └── .gitkeep ├── PowerDelivery3.template.nuspec ├── PowerDelivery3Node.template.nuspec ├── README.md ├── Release.ps1 ├── Scripts ├── Chocolatey │ ├── ChocolateyPowerDeliveryUtils.ps1 │ ├── PowerDelivery │ │ ├── ChocolateyInstall.ps1 │ │ ├── ChocolateyUninstall.ps1 │ │ └── Init.ps1 │ └── PowerDeliveryNode │ │ ├── ChocolateyInstall.ps1 │ │ ├── ChocolateyUninstall.ps1 │ │ └── Init.ps1 └── Nodes │ └── AllowDelivery.ps1 ├── Test.ps1 ├── Tests ├── GetKeyBytes.Tests.ps1 ├── GetProjectDirectory.Tests.ps1 ├── New-DeliveryKey.Tests.ps1 ├── New-DeliveryRole.Tests.ps1 └── ValidateNewFileName.Tests.ps1 └── Verify.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.nupkg 3 | ~$* 4 | Thumbs.db 5 | Desktop.ini 6 | .DS_Store 7 | gh-pages/ 8 | _site 9 | PowerDelivery3.nuspec 10 | PowerDelivery3Node.nuspec -------------------------------------------------------------------------------- /Modules/PowerDelivery/Cmdlets/Enter-DeliveryRole.ps1: -------------------------------------------------------------------------------- 1 | function Enter-DeliveryRole { 2 | [CmdletBinding()] 3 | param( 4 | [Parameter(Position=0, Mandatory=1)][scriptblock] $Up, 5 | [Parameter(Position=1, Mandatory=0)][scriptblock] $Down 6 | ) 7 | $fullPath = [System.IO.Path]::GetDirectoryName($MyInvocation.PSCommandPath) 8 | $roleName = [System.IO.Path]::GetFileName($fullPath) 9 | 10 | $pow.roles.Add($roleName, @($Up, $Down)) 11 | } 12 | 13 | Set-Alias Delivery:Role Enter-DeliveryRole 14 | Export-ModuleMember -Function Enter-DeliveryRole -Alias Delivery:Role -------------------------------------------------------------------------------- /Modules/PowerDelivery/Cmdlets/Invoke-MSBuild.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Compiles a project using msbuild.exe. 4 | 5 | .Description 6 | The Invoke-MSBuild cmdlet is used to compile a MSBuild-compatible project or solution. You should always use this cmdlet instead of a direct call to msbuild.exe or existing cmdlets you may have found online when working with powerdelivery. 7 | 8 | This cmdlet provides the following essential continuous delivery features: 9 | 10 | Updates the version of any AssemblyInfo.cs (or AssemblyInfo.vb) files with the current build version. This causes all of your binaries to have the build number. For example, if your build pipeline's version in the script is set to 1.0.2 and this is a build against changeset C234, the version of your assemblies will be set to 1.0.2.234. 11 | 12 | Automatically targets a build configuration matching the environment name ("Development", "Test", or "Production"). Create build configurations named "Development", "Test", and "Production" with appropriate settings in your projects for this to work. If you don't want this, you'll have to explicitly pass the configuration as a parameter. 13 | 14 | Reports the status of the compilation back to TFS to be viewed in the build summary. This is important because it allows tests run using mstest.exe to have their run results associated with the compiled assets created using this cmdlet. 15 | 16 | .Example 17 | Invoke-MSBuild MyProject/MySolution.sln -properties @{MyCustomProp = SomeValue} 18 | 19 | .Parameter projectFile 20 | A relative path at or below the script directory that specifies an MSBuild project or solution to compile. 21 | 22 | .Parameter properties 23 | Optional. A PowerShell hash containing name/value pairs to set as MSBuild properties. 24 | 25 | .Parameter target 26 | Optional. The name of the MSBuild target to invoke in the project file. Defaults to the default target specified within the project file. 27 | 28 | .Parameter toolsVersion 29 | Optional. The version of MSBuild to run ("2.0", "3.5", "4.0", etc.). The default is "4.0". 30 | 31 | .Parameter verbosity 32 | Optional. The verbosity of this MSBuild compilation. The default is "m". 33 | 34 | .Parameter buildConfiguration 35 | Optional. The default is to use the same as the environment name. Create build configurations named "Development", "Test", and "Production" with appropriate settings in your projects. 36 | 37 | .Parameter flavor 38 | Optional. The platform configuration (x86, x64 etc.) of this MSBuild complation. The default is "AnyCPU". 39 | 40 | .Parameter ignoreProjectExtensions 41 | Optional. A semicolon-delimited list of project extensions (".smproj;.csproj" etc.) of projects in the solution to not compile. 42 | #> 43 | function Invoke-MSBuild 44 | { 45 | [CmdletBinding()] 46 | param( 47 | [Parameter(Position=0,Mandatory=1)][string] $projectFile, 48 | [Parameter(Position=1,Mandatory=0)] $properties = @{}, 49 | [Parameter(Position=2,Mandatory=0)][string] $target, 50 | [Parameter(Position=3,Mandatory=0)][string] $toolsVersion = '4.0', 51 | [Parameter(Position=4,Mandatory=0)][string] $verbosity = "m", 52 | [Parameter(Position=5,Mandatory=0)][string] $buildConfiguration, 53 | [Parameter(Position=6,Mandatory=0)][string] $flavor = "AnyCPU", 54 | [Parameter(Position=7,Mandatory=0)][string] $ignoreProjectExtensions 55 | ) 56 | 57 | if ([String]::IsNullOrWhiteSpace($buildConfiguration)) 58 | { 59 | if ($pow.EnvironmentName -eq 'Local') 60 | { 61 | $buildConfiguration = 'Debug' 62 | } 63 | else 64 | { 65 | $buildConfiguration = 'Release' 66 | } 67 | 68 | if (!$properties.ContainsKey('Configuration')) 69 | { 70 | $properties.Add('Configuration', $buildConfiguration) 71 | } 72 | } 73 | else 74 | { 75 | $properties.Add('Configuration', $buildConfiguration) 76 | } 77 | 78 | $regKey = "HKLM:\Software\Microsoft\MSBuild\ToolsVersions\$toolsVersion" 79 | $regProperty = "MSBuildToolsPath" 80 | $regKeyProperty = $null 81 | 82 | try 83 | { 84 | $regKeyProperty = (Get-ItemProperty $regKey) 85 | } 86 | catch 87 | { 88 | Write-Host ".NET ToolsVersion $toolsVersion not found in the registry. Is this version not installed?" 89 | throw 90 | } 91 | 92 | $msbuildExe = Join-Path -path $regKeyProperty.$regProperty -childpath "msbuild.exe" 93 | 94 | $msBuildCommand = """$msbuildExe""" 95 | $msBuildCommand += " /nologo /m" 96 | 97 | if ($properties.length -gt 0) 98 | { 99 | $properties.Keys | % { 100 | $msBuildCommand += " ""/p:$($_)=$($properties.Item($_))""" 101 | } 102 | } 103 | 104 | if ([string]::IsNullOrWhiteSpace($toolsVersion) -eq $false) 105 | { 106 | $msBuildCommand += " ""/tv:$toolsVersion""" 107 | } 108 | 109 | $msBuildCommand += " `"/consoleloggerparameters:Verbosity=q`"" 110 | 111 | if ([string]::IsNullOrWhiteSpace($verbosity) -eq $false) 112 | { 113 | $msBuildCommand += " /v:$verbosity" 114 | } 115 | 116 | if ([string]::IsNullOrWhiteSpace($ignoreProjectExtensions) -eq $false) 117 | { 118 | $msBuildCommand += " ""/ignore:$ignoreProjectExtensions""" 119 | } 120 | 121 | if (![string]::IsNullOrWhiteSpace($target)) 122 | { 123 | $msBuildCommand += " ""/T:$target""" 124 | } 125 | 126 | $currentDirectory = Get-Location 127 | 128 | $shortPath = [IO.Path]::GetDirectoryName($projectFile) 129 | $fullPath = Join-Path $currentDirectory $shortPath 130 | $fullPathNormalized = [IO.Path]::GetFullPath($fullPath) 131 | 132 | $projectFileName = [IO.Path]::GetFileName($projectFile) 133 | $fullProjectFile = Join-Path $fullPathNormalized $projectFileName 134 | $projectFileBase = [IO.Path]::GetFileNameWithoutExtension($projectFileName) 135 | $logFile = Join-Path $fullPathNormalized "$($projectFileBase).log" 136 | 137 | $msBuildCommand += " ""/l:FileLogger,Microsoft.Build.Engine;logfile=$logFile""" 138 | $msBuildCommand += " ""$fullProjectFile""" 139 | 140 | #Update-AssemblyInfoFiles -path $shortPath 141 | 142 | Set-Location $fullPathNormalized 143 | 144 | Invoke-Expression "& $msBuildCommand" | Out-Null 145 | 146 | if ($lastexitcode -ne 0) 147 | { 148 | $errorMessage = "Invoke-MSBuild failed. Check your parameters or try again with verbose output." 149 | 150 | if (![String]::IsNullOrWhitespace($logFile)) 151 | { 152 | $errorMessage += " See $logFile for details." 153 | } 154 | 155 | throw $errorMessage 156 | } 157 | } 158 | 159 | Export-ModuleMember -Function Invoke-MSBuild -------------------------------------------------------------------------------- /Modules/PowerDelivery/Cmdlets/New-DeliveryCredential.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Encrypts a set of credentials with a key and adds them to a powerdelivery project. 4 | 5 | .Description 6 | Encrypts a set of credentials with a key and adds them to a powerdelivery project. 7 | 8 | .Example 9 | New-DeliveryCredentials MyKey "MYDOMAIN\myuser" 10 | 11 | The credential will be created at the path: 12 | 13 | .\Secrets\\Credentials\.credential 14 | 15 | .Parameter KeyName 16 | The name of the key to use for encryption. 17 | 18 | .Parameter UserName 19 | The username of the account to encrypt the password for. 20 | 21 | .Parameter Force 22 | Switch that forces overwrite of any existing credential file if found. 23 | #> 24 | function New-DeliveryCredential { 25 | [CmdletBinding()] 26 | param ( 27 | [Parameter(Position=0,Mandatory=1)][string] $KeyName, 28 | [Parameter(Position=1,Mandatory=1)][string] $UserName, 29 | [Parameter(Position=2,Mandatory=0)][switch] $Force 30 | ) 31 | 32 | $projectDir = GetProjectDirectory 33 | $projectName = [IO.Path]::GetFileName($projectDir) 34 | 35 | $userFileName = $UserName -replace '\\', '#' 36 | 37 | ValidateNewFileName -FileName $userFileName -Description "username" 38 | 39 | $keyBytes = GetKeyBytes -ProjectDir $projectName -KeyName $KeyName -ThrowOnError 40 | 41 | $credentialsPath = Join-Path (Get-Location) "Secrets\$KeyName\Credentials" 42 | if (!(Test-Path $credentialsPath)) { 43 | New-Item $credentialsPath -ItemType Directory | Out-Null 44 | } 45 | 46 | $userNameFile = "$($userFileName).credential" 47 | $userNamePath = Join-Path $credentialsPath $userNameFile 48 | 49 | if ((Test-Path $userNamePath) -and (!$Force)) { 50 | throw "Credential at $userNamePath exists. Pass -Force to overwrite." 51 | } 52 | 53 | Write-Host "Enter the password for $userName and press ENTER:" 54 | try { 55 | Read-Host -AsSecureString | ConvertFrom-SecureString -Key $keyBytes | Out-File $userNamePath -Force 56 | Write-Host "Credential written to "".\Secrets\$KeyName\Credentials\$userNameFile""" 57 | } 58 | catch { 59 | "Key $KeyName appears to be invalid - $_" 60 | } 61 | } 62 | 63 | Export-ModuleMember -Function New-DeliveryCredential 64 | -------------------------------------------------------------------------------- /Modules/PowerDelivery/Cmdlets/New-DeliveryKey.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Generates a powerdelivery key file used to encrypt credentials. 4 | 5 | .Description 6 | Generates a powerdelivery key file used to encrypt credentials. 7 | 8 | The key will be created at the path: 9 | 10 | :\Users\\Documents\PowerDelivery\Keys\\.key 11 | 12 | .Example 13 | New-DeliveryKey MyKey 14 | 15 | .Parameter KeyName 16 | The name of the key to generate. 17 | #> 18 | function New-DeliveryKey { 19 | [CmdletBinding()] 20 | param( 21 | [Parameter(Position=0,Mandatory=1)][string] $KeyName 22 | ) 23 | 24 | ValidateNewFileName -FileName $KeyName -Description "key name" 25 | $projectDir = GetProjectDirectory 26 | $projectName = [IO.Path]::GetFileName($projectDir) 27 | 28 | $keyString = GetNewKey 29 | 30 | $myDocumentsFolder = GetMyDocumentsFolder 31 | $keysFolderPath = Join-Path $myDocumentsFolder "PowerDelivery\Keys\$projectName" 32 | $keyFilePath = Join-Path $keysFolderPath "$KeyName.key" 33 | 34 | if (Test-Path $keyFilePath) { 35 | throw "Key $keyFilePath already exists." 36 | } 37 | 38 | if (!(Test-Path $keysFolderPath)) { 39 | New-Item $keysFolderPath -ItemType Directory | Out-Null 40 | } 41 | 42 | $keyString | Out-File -FilePath $keyFilePath 43 | 44 | Write-Host "Key written to ""$keyFilePath""" 45 | } 46 | 47 | Export-ModuleMember -Function New-DeliveryKey 48 | -------------------------------------------------------------------------------- /Modules/PowerDelivery/Cmdlets/New-DeliveryProject.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Creates a new powerdelivery project. 4 | 5 | .Description 6 | The New-DeliveryProject cmdlet generates the files needed to work with powerdelivery. 7 | 8 | .Example 9 | New-DeliveryProject MyApp 'Local', 'Test', 'Production' 10 | 11 | .Parameter ProjectName 12 | The name of the project to create. 13 | 14 | .Parameter Environments 15 | An array of the names of environments to create configuration variable and environment scripts for. 16 | #> 17 | function New-DeliveryProject { 18 | [CmdletBinding()] 19 | param ( 20 | [Parameter(Position=0,Mandatory=1)][Alias('p')][string] $ProjectName, 21 | [Parameter(Position=1,Mandatory=0)][Alias('e')] $Environments 22 | ) 23 | 24 | ValidateNewFileName -FileName $ProjectName -Description "project name" 25 | 26 | if ($Environments -ne $null) { 27 | foreach ($environment in $Environments) { 28 | ValidateNewFileName -FileName $environment -Description "environment name" 29 | } 30 | } 31 | 32 | $templatesPath = Join-Path $pow.scriptDir "Templates" 33 | 34 | $projectDir = "$($ProjectName)Delivery" 35 | 36 | if (Test-Path $projectDir) { 37 | throw "Directory $projectDir already exists." 38 | } 39 | 40 | $configurationDir = "$projectDir\Configuration" 41 | $environmentsDir = "$projectDir\Environments" 42 | $rolesDir = "$projectDir\Roles" 43 | $secretsDir = "$projectDir\Secrets" 44 | $targetsDir = "$projectDir\Targets" 45 | 46 | $dirsToCreate = @($configurationDir, $secretsDir, $environmentsDir, $rolesDir, $targetsDir) 47 | 48 | # Create directories 49 | foreach ($dirToCreate in $dirsToCreate) { 50 | New-Item $dirToCreate -ItemType Directory | Out-Null 51 | } 52 | 53 | # Copy the default target script 54 | Copy-Item "$templatesPath\Target.ps1.template" "$targetsDir\Release.ps1" 55 | 56 | # Copy the shared configuration variables script 57 | Copy-Item "$templatesPath\_Shared.ps1.template" "$configurationDir\_Shared.ps1" 58 | 59 | if ($Environments -ne $null) { 60 | foreach ($environment in $Environments) { 61 | 62 | # Copy the environment configuration variables script 63 | Copy-Item "$templatesPath\Configuration.ps1.template" "$configurationDir\$environment.ps1" 64 | 65 | # Copy the environment nodes script 66 | Copy-Item "$templatesPath\Environment.ps1.template" "$environmentsDir\$environment.ps1" 67 | } 68 | } 69 | 70 | Write-Host "Project successfully created at "".\$($ProjectName)Delivery""" 71 | } 72 | 73 | Export-ModuleMember -Function New-DeliveryProject -------------------------------------------------------------------------------- /Modules/PowerDelivery/Cmdlets/New-DeliveryRole.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Generates a new powerdelivery role. 4 | 5 | .Description 6 | Generates a new powerdelivery role. 7 | 8 | The role will be created at the path: 9 | 10 | .\Roles\ 11 | 12 | .Example 13 | New-DeliveryRole Database 14 | 15 | .Parameter RoleNames 16 | A comma-separated list of one or more names of roles to create. 17 | #> 18 | function New-DeliveryRole { 19 | param( 20 | [Parameter(Position=1,Mandatory=1)][string[]] $RoleName 21 | ) 22 | 23 | $projectDir = GetProjectDirectory 24 | 25 | ValidateNewFileName -FileName $RoleName -Description "role name" 26 | 27 | $templatesPath = Join-Path $pow.scriptDir "Templates" 28 | 29 | foreach ($role in $RoleName) { 30 | 31 | $rolePath = ".\Roles\$role" 32 | $roleDir = Join-Path $projectDir "Roles\$role" 33 | $migrationsDir = Join-Path $roleDir "Migrations" 34 | 35 | if (Test-Path $roleDir) { 36 | throw "Directory $rolePath already exists." 37 | } 38 | 39 | New-Item $migrationsDir -ItemType Directory | Out-Null 40 | 41 | # Copy the role script 42 | Copy-Item "$templatesPath\Role.ps1.template" "$roleDir\Always.ps1" 43 | 44 | Write-Host "Role created at ""$rolePath""" 45 | } 46 | } 47 | 48 | Export-ModuleMember -Function New-DeliveryRole 49 | -------------------------------------------------------------------------------- /Modules/PowerDelivery/Cmdlets/New-DeliverySecret.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Encrypts a secret with a key and adds it to to a powerdelivery project. 4 | 5 | .Description 6 | Encrypts a secret with a key and adds it to to a powerdelivery project. 7 | 8 | The secret will be created at the path: 9 | 10 | .\Secrets\\.secret 11 | 12 | .Example 13 | New-DeliverySecret MyKey MySecret 14 | 15 | .Parameter KeyName 16 | The name of the key to use for encryption. 17 | 18 | .Parameter SecretName 19 | The name of the secret to encrypt. 20 | 21 | .Parameter Force 22 | Switch that forces overwrite of any existing secret file if found. 23 | #> 24 | function New-DeliverySecret { 25 | [CmdletBinding()] 26 | param ( 27 | [Parameter(Position=0,Mandatory=1)][string] $KeyName, 28 | [Parameter(Position=1,Mandatory=1)][string] $SecretName, 29 | [Parameter(Position=2,Mandatory=0)][switch] $Force 30 | ) 31 | 32 | $projectDir = GetProjectDirectory 33 | $projectName = [IO.Path]::GetFileName($projectDir) 34 | 35 | ValidateNewFileName -FileName $SecretName -Description "secret name" 36 | 37 | $keyBytes = GetKeyBytes -ProjectDir $projectName -KeyName $KeyName -ThrowOnError 38 | 39 | $secretsPath = Join-Path (Get-Location) "Secrets\$KeyName" 40 | if (!(Test-Path $secretsPath)) { 41 | New-Item $secretsPath -ItemType Directory | Out-Null 42 | } 43 | 44 | $secretFile = "$(SecretName).secret" 45 | $secretPath = Join-Path $secretsPath $secretFile 46 | if ((Test-Path $secretPath) -and (!$Force)) { 47 | throw "Secret at $secretPath exists. Pass -Force to overwrite." 48 | } 49 | 50 | Write-Host "Enter the secret value $SecretKey and press ENTER:" 51 | try { 52 | Read-Host -AsSecureString | ConvertFrom-SecureString -Key $keyBytes | Out-File $secretPath -Force 53 | Write-Host "Secret written to "".\Secrets\$KeyName\$secretFile""" 54 | } 55 | catch { 56 | "Key $KeyName appears to be invalid - $_" 57 | } 58 | } 59 | 60 | Export-ModuleMember -Function New-DeliverySecret 61 | -------------------------------------------------------------------------------- /Modules/PowerDelivery/Cmdlets/Publish-DeliveryFilesToAzure.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Uploads files for a powerdelivery release to Windows Azure for use by nodes that will host the product. 4 | 5 | .Description 6 | Uploads files for a powerdelivery release to Windows Azure for use by nodes that will host the product. 7 | All files that are uploaded are prefixed with a path that contains the name of the powerdelivery project and a 8 | timestamp of the date and time that the target started. 9 | 10 | .Example 11 | Delivery:Role { 12 | param($target, $config, $node) 13 | 14 | # Recursively uploads files within the folder "MyApp\bin\Release" to a Windows Azure 15 | # storage container below a \ path. 16 | Publish-DeliveryFilesToAzure -Path "MyApp\bin\Debug" ` 17 | -Destination "MyApp" ` 18 | -Credential $target.Credentials['admin@myazuredomain.com'] ` 19 | -SubscriptionId $config.MyAzureSubsciptionId ` 20 | -StorageAccountName $config.MyAzureStorageAccountName ` 21 | -StorageAccountKey $config.MyAzureStorageAccountKey ` 22 | -StorageContainer $config.MyAzureStorageContainer ` 23 | -Recurse 24 | } 25 | 26 | .Parameter Path 27 | The path of files to upload relative to the directory above your powerdelivery project. 28 | 29 | .Parameter Destination 30 | The directory in which to place uploaded files. 31 | 32 | .Parameter Credential 33 | The Windows Azure account credentials to use. 34 | 35 | .Parameter SubscriptionId 36 | A Windows Azure subscription that the account in the Credential parameter is permitted 37 | to use. 38 | 39 | .Parameter StorageAccountName 40 | A Windows Azure storage account that the account in the Credential parameter is permitted 41 | to access. 42 | 43 | .Parameter StorageAccountKey 44 | A Windows Azure storage account key that matches the StorageAccountName parameter providing 45 | read and write access. 46 | 47 | .Parameter StorageContainer 48 | A container within the Windows Azure storage account referred to in the StorageAccountName 49 | parameter into which to upload files. 50 | 51 | .Parameter Filter 52 | A comma-separated list of file extensions to filter for. Others will be excluded. 53 | 54 | .Parameter Include 55 | A comma-separated list of paths to include. Others will be excluded. 56 | 57 | .Parameter Exclude 58 | A comma-separated list of paths to exclude. Others will be included. 59 | 60 | .Parameter Recurse 61 | Uploads files in subdirectories below the directory specified by the Path parameter. 62 | 63 | .Parameter Keep 64 | The number of previous releases to keep. Defaults to 5. 65 | #> 66 | function Publish-DeliveryFilesToAzure { 67 | param( 68 | [Parameter(Position=0,Mandatory=1)][string] $Path, 69 | [Parameter(Position=1,Mandatory=1)][string] $Destination, 70 | [Parameter(Position=2,Mandatory=1)][PSCredential] $Credential, 71 | [Parameter(Position=3,Mandatory=1)][string] $SubscriptionId, 72 | [Parameter(Position=4,Mandatory=1)][string] $StorageAccountName, 73 | [Parameter(Position=5,Mandatory=1)][string] $StorageAccountKey, 74 | [Parameter(Position=6,Mandatory=1)][string] $StorageContainer, 75 | [Parameter(Position=7,Mandatory=0)][string] $Filter, 76 | [Parameter(Position=8,Mandatory=0)][string[]] $Include, 77 | [Parameter(Position=9,Mandatory=0)][string[]] $Exclude, 78 | [Parameter(Position=10,Mandatory=0)][switch] $Recurse, 79 | [Parameter(Position=11,Mandatory=0)][int] $Keep = 5 80 | ) 81 | 82 | $verbose = Test-Verbose 83 | 84 | if (-not ("win32.Shell" -as [type])) { 85 | Add-Type -Namespace win32 -Name Shell -MemberDefinition @" 86 | [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)] 87 | public static extern bool PathRelativePathTo(System.Text.StringBuilder lpszDst, 88 | string From, System.IO.FileAttributes attrFrom, String to, System.IO.FileAttributes attrTo); 89 | "@ 90 | } 91 | 92 | Import-Module Azure 93 | 94 | # Set the active Azure account 95 | Add-AzureAccount -Credential $Credential | Out-Null 96 | 97 | if ($verbose) { 98 | Write-Host "Using Azure subscription ""$SubscriptionId""" 99 | } 100 | 101 | # Set the active subscription 102 | Select-AzureSubscription -SubscriptionId $SubscriptionId 103 | 104 | if ($verbose) { 105 | Write-Host "Using Azure storage account ""$StorageAccountName""" 106 | } 107 | 108 | # Connect to the Azure storage account 109 | $storageContext = New-AzureStorageContext -StorageAccountName $StorageAccountName ` 110 | -StorageAccountKey $StorageAccountKey 111 | 112 | 113 | # Get the powerdelivery share 114 | $releasesContainer = Get-AzureStorageContainer -Name $StorageContainer ` 115 | -Context $storageContext ` 116 | -ErrorAction SilentlyContinue 117 | if (!$releasesContainer) { 118 | throw "Azure storage container $StorageContainer not found in account $StorageAccountName." 119 | } 120 | 121 | $appDataDir = [Environment]::GetFolderPath("ApplicationData") 122 | $tempGuidDir = Join-Path $appDataDir "PowerDelivery\$([System.Guid]::NewGuid())" 123 | $tempProjectDir = Join-Path $tempGuidDir $target.ProjectName 124 | $tempReleaseDir = Join-Path $tempProjectDir $target.StartedAt 125 | 126 | New-Item -ItemType Directory $tempReleaseDir | Out-Null 127 | 128 | try { 129 | 130 | # Check if path is directory 131 | $pathIsDirectory = (Get-Item $Path) -is [System.IO.DirectoryInfo] 132 | 133 | if ($pathIsDirectory) { 134 | 135 | $copyArgs = @{ 136 | Path = $Path; 137 | Destination = "$tempReleaseDir\$Destination"; 138 | Filter = $Filter; 139 | Exclude = $Exclude; 140 | Include = $Include 141 | } 142 | 143 | if ($Recurse) { 144 | $copyArgs.Add('Recurse', $Recurse) 145 | } 146 | 147 | # Copy files to temp dir 148 | Copy-Item @copyArgs | Out-Null 149 | } 150 | else { 151 | 152 | # Copy file to temp dir 153 | Copy-Item $Path "$tempReleaseDir\$Destination" | Out-Null 154 | } 155 | 156 | # Upload to azure 157 | Set-Location $tempGuidDir 158 | foreach ($file in (Get-ChildItem . -Recurse -File)) { 159 | 160 | $relativePath = New-Object -TypeName System.Text.StringBuilder 260 161 | [win32.Shell]::PathRelativePathTo($relativePath, $tempProjectDir, [System.IO.FileAttributes]::Normal, $file.FullName, [System.IO.FileAttributes]::Normal) | Out-Null 162 | 163 | Set-AzureStorageBlobContent -File $file.FullName ` 164 | -Blob $relativePath.ToString() ` 165 | -Container $StorageContainer ` 166 | -Context $storageContext | Out-Null 167 | } 168 | 169 | # Get all release files 170 | $allReleaseFiles = Get-AzureStorageBlob -Blob "$($target.ProjectName)*" ` 171 | -Container $StorageContainer ` 172 | -Context $storageContext 173 | 174 | $releases = @() 175 | 176 | # Iterate release files to find releases 177 | foreach ($releaseFile in $allReleaseFiles) { 178 | $pathSegments = $releaseFile.Name -split '/' 179 | $releaseSegment = $pathSegments[1] 180 | if (!($releases -contains $releaseSegment)) { 181 | $releases += $releaseSegment 182 | } 183 | } 184 | 185 | # If we have releases to delete 186 | if ($releases.count -gt $Keep) { 187 | 188 | # Determine how many to remove 189 | $oldReleaseCount = $releases.count - $Keep 190 | 191 | # Get the releases to delete 192 | $releasesToDelete = $releases | Sort-Object | Select -First $oldReleaseCount 193 | 194 | # Iterate release files 195 | foreach ($releaseFile in $allReleaseFiles) { 196 | 197 | # Iterate releases to delete 198 | foreach ($releaseToDelete in $releasesToDelete) { 199 | 200 | $releasePrefix = "$($target.ProjectName)/$releaseToDelete" 201 | 202 | # Check whether blob name starts with release prefix 203 | if ($releaseFile.Name.StartsWith($releasePrefix)) { 204 | 205 | # Delete the blob 206 | Remove-AzureStorageBlob -Blob $releaseFile.Name ` 207 | -Container $StorageContainer ` 208 | -Context $storageContext ` 209 | -Force | Out-Null 210 | } 211 | } 212 | } 213 | } 214 | } 215 | finally { 216 | Set-Location $target.StartDir 217 | 218 | # Cleanup 219 | Remove-Item $tempGuidDir -Recurse -Force 220 | } 221 | } 222 | 223 | Export-ModuleMember -Function Publish-DeliveryFilesToAzure -------------------------------------------------------------------------------- /Modules/PowerDelivery/Cmdlets/Publish-DeliveryFilesToS3.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Uploads files for a powerdelivery release to AWS Simple Storage Service for use by nodes that will host the product. 4 | 5 | .Description 6 | Uploads files for a powerdelivery release to AWS Simple Storage Service for use by nodes that will host the product. 7 | All files that are uploaded are prefixed with a path that contains the name of the powerdelivery project and a 8 | timestamp of the date and time that the target started. 9 | 10 | .Example 11 | Delivery:Role { 12 | param($target, $config, $node) 13 | 14 | # Recursively uploads files within the folder "MyApp\bin\Release" 15 | # to an AWS S3 bucket below a \ path. 16 | Publish-DeliveryFilesToS3 -Path "MyApp\bin\Debug" ` 17 | -Destination "MyApp" ` 18 | -ProfileName "MyProfile" ` 19 | -BucketName "MyAppReleases" ` 20 | -Recurse 21 | } 22 | 23 | .Parameter Path 24 | The path of files to upload relative to the directory above your powerdelivery project. 25 | 26 | .Parameter Destination 27 | The directory in which to place uploaded files. 28 | 29 | .Parameter ProfileName 30 | The name of the AWS profile containing credentials to use. 31 | See https://docs.aws.amazon.com/powershell/latest/userguide/specifying-your-aws-credentials.html 32 | 33 | .Parameter BucketName 34 | The name of the S3 bucket to publish the release to. 35 | 36 | .Parameter Filter 37 | A comma-separated list of file extensions to filter for. Others will be excluded. 38 | 39 | .Parameter Include 40 | A comma-separated list of paths to include. Others will be excluded. 41 | 42 | .Parameter Exclude 43 | A comma-separated list of paths to exclude. Others will be included. 44 | 45 | .Parameter Recurse 46 | Uploads files in subdirectories below the directory specified by the Path parameter. 47 | 48 | .Parameter Keep 49 | The number of previous releases to keep. Defaults to 5. 50 | 51 | .Parameter ProfilesLocation 52 | The location to look in for the AWS profile containing credentials. 53 | See https://docs.aws.amazon.com/powershell/latest/userguide/specifying-your-aws-credentials.html 54 | #> 55 | function Publish-DeliveryFilesToS3 { 56 | param( 57 | [Parameter(Position=0,Mandatory=1)][string] $Path, 58 | [Parameter(Position=1,Mandatory=1)][string] $Destination, 59 | [Parameter(Position=2,Mandatory=1)][string] $ProfileName, 60 | [Parameter(Position=3,Mandatory=1)][string] $BucketName, 61 | [Parameter(Position=4,Mandatory=0)][string] $Filter, 62 | [Parameter(Position=5,Mandatory=0)][string[]] $Include, 63 | [Parameter(Position=6,Mandatory=0)][string[]] $Exclude, 64 | [Parameter(Position=7,Mandatory=0)][switch] $Recurse, 65 | [Parameter(Position=8,Mandatory=0)][int] $Keep = 5, 66 | [Parameter(Position=9,Mandatory=0)][string] $ProfilesLocation 67 | ) 68 | 69 | $verbose = Test-Verbose 70 | 71 | if (-not ("win32.Shell" -as [type])) { 72 | Add-Type -Namespace win32 -Name Shell -MemberDefinition @" 73 | [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)] 74 | public static extern bool PathRelativePathTo(System.Text.StringBuilder lpszDst, 75 | string From, System.IO.FileAttributes attrFrom, String to, System.IO.FileAttributes attrTo); 76 | "@ 77 | } 78 | 79 | Import-Module AWSPowerShell 80 | 81 | $setAwsCredentialsArgs = @{ 82 | ProfileName = $ProfileName 83 | } 84 | 85 | if (![String]::IsNullOrEmpty($ProfilesLocation)) { 86 | $setAwsCredentialsArgs.Add("ProfilesLocation", $ProfilesLocation) 87 | } 88 | 89 | # Set the active AWS credentials 90 | Set-AwsCredentials @$setAwsCredentialsArgs 91 | 92 | if ($verbose) { 93 | Write-Host "Using AWS profile ""$ProfileName""" 94 | } 95 | 96 | # Connect to the S3 bucket 97 | $bucket = Get-S3Bucket $BucketName 98 | if (!$bucket) { 99 | throw "S3 bucket $BucketName not found." 100 | } 101 | 102 | $appDataDir = [Environment]::GetFolderPath("ApplicationData") 103 | $tempGuidDir = Join-Path $appDataDir "PowerDelivery\$([System.Guid]::NewGuid())" 104 | $tempProjectDir = Join-Path $tempGuidDir $target.ProjectName 105 | $tempReleaseDir = Join-Path $tempProjectDir $target.StartedAt 106 | 107 | New-Item -ItemType Directory $tempReleaseDir | Out-Null 108 | 109 | try { 110 | 111 | # Check if path is directory 112 | $pathIsDirectory = (Get-Item $Path) -is [System.IO.DirectoryInfo] 113 | 114 | if ($pathIsDirectory) { 115 | 116 | $copyArgs = @{ 117 | Path = $Path; 118 | Destination = "$tempReleaseDir\$Destination"; 119 | Filter = $Filter; 120 | Exclude = $Exclude; 121 | Include = $Include 122 | } 123 | 124 | if ($Recurse) { 125 | $copyArgs.Add('Recurse', $Recurse) 126 | } 127 | 128 | # Copy files to temp dir 129 | Copy-Item @copyArgs | Out-Null 130 | } 131 | else { 132 | 133 | # Copy file to temp dir 134 | Copy-Item $Path "$tempReleaseDir\$Destination" | Out-Null 135 | } 136 | 137 | # Upload to S3 138 | Set-Location $tempGuidDir 139 | foreach ($file in (Get-ChildItem . -Recurse -File)) { 140 | 141 | $relativePath = New-Object -TypeName System.Text.StringBuilder 260 142 | [win32.Shell]::PathRelativePathTo($relativePath, $tempProjectDir, [System.IO.FileAttributes]::Normal, $file.FullName, [System.IO.FileAttributes]::Normal) | Out-Null 143 | 144 | Write-S3Object -BucketName $BucketName ` 145 | -Key $relativePath.ToString() ` 146 | -File $file.FullName | Out-Null 147 | } 148 | 149 | # Get all release files 150 | $allReleaseFiles = Get-S3Object -BucketName $BucketName ` 151 | -KeyPrefix $target.ProjectName 152 | 153 | $releases = @() 154 | 155 | # Iterate release files to find releases 156 | foreach ($releaseFile in $allReleaseFiles) { 157 | $pathSegments = $releaseFile.Key -split '/' 158 | $releaseSegment = $pathSegments[1] 159 | if (!($releases -contains $releaseSegment)) { 160 | $releases += $releaseSegment 161 | } 162 | } 163 | 164 | # If we have releases to delete 165 | if ($releases.count -gt $Keep) { 166 | 167 | # Determine how many to remove 168 | $oldReleaseCount = $releases.count - $Keep 169 | 170 | # Get the releases to delete 171 | $releasesToDelete = $releases | Sort-Object | Select -First $oldReleaseCount 172 | 173 | # Iterate release files 174 | foreach ($releaseFile in $allReleaseFiles) { 175 | 176 | # Iterate releases to delete 177 | foreach ($releaseToDelete in $releasesToDelete) { 178 | 179 | $releasePrefix = "$($target.ProjectName)/$releaseToDelete" 180 | 181 | # Check whether s3 object key starts with release prefix 182 | if ($releaseFile.Key.StartsWith($releasePrefix)) { 183 | 184 | # Delete the S3 object 185 | Remove-S3Object -BucketName $BucketName ` 186 | -Key $releaseFile.Key ` 187 | -Force | Out-Null 188 | } 189 | } 190 | } 191 | } 192 | } 193 | finally { 194 | Set-Location $target.StartDir 195 | 196 | # Cleanup 197 | Remove-Item $tempGuidDir -Recurse -Force 198 | } 199 | } 200 | 201 | Export-ModuleMember -Function Publish-DeliveryFilesToS3 -------------------------------------------------------------------------------- /Modules/PowerDelivery/Cmdlets/Start-Delivery.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Starts deployment or rollback of a target with powerdelivery. 4 | 5 | .Description 6 | Starts deployment or rollback of a target with powerdelivery. Must be run in the parent directory of your powerdelivery project. 7 | 8 | .Example 9 | Start-Delivery MyApp Release Production 10 | 11 | .Parameter ProjectName 12 | The name of the project. Powerdelivery looks for a subdirectory with this name suffixed with "Delivery". 13 | 14 | .Parameter TargetName 15 | The name of the target to run. Must match the name of a file in the Targets subdirectory of your powerdelivery project without the file extension. 16 | 17 | .Parameter EnvironmentName 18 | The name of the environment to target during the run. Must match the name of a file in the Environments subdirectory of your powerdelivery project without the file extension. 19 | 20 | .Parameter Properties 21 | A hash of properties to pass to the target. Typically used to pass information from build servers. 22 | 23 | .Parameter Rollback 24 | Switch that when set causes the Down block of roles to be called instead of Up, performing a rollback. 25 | #> 26 | function Start-Delivery { 27 | [CmdletBinding()] 28 | param ( 29 | [Parameter(Position=0,Mandatory=1)][string] $ProjectName, 30 | [Parameter(Position=1,Mandatory=1)][string] $TargetName, 31 | [Parameter(Position=2,Mandatory=1)][string] $EnvironmentName, 32 | [Parameter(Position=3,Mandatory=0)][hashtable] $Properties = @{}, 33 | [Parameter(Position=4,Mandatory=0)][switch] $Rollback 34 | ) 35 | 36 | $ErrorActionPreference = 'Stop' 37 | 38 | winrm quickconfig -Force | Out-Null 39 | Enable-PSRemoting -Force -SkipNetworkProfileCheck | Out-Null 40 | 41 | # Verify running as Administrator 42 | $user = [Security.Principal.WindowsIdentity]::GetCurrent(); 43 | if (!(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) { 44 | throw "Please run PowerDelivery using an elevated (Administrative) command prompt." 45 | } 46 | 47 | $pow.colors = @{ 48 | SuccessForeground = 'Green'; 49 | FailureForeground = 'Red'; 50 | StepForeground = 'Magenta'; 51 | RoleForeground = 'Yellow'; 52 | CommandForeground = 'White'; 53 | LogFileForeground = 'White' 54 | } 55 | 56 | $pow.target = @{ 57 | ProjectName = $ProjectName; 58 | TargetName = $TargetName; 59 | EnvironmentName = $EnvironmentName; 60 | Revision = $Revision; 61 | RequestedBy = (whoami).ToUpper(); 62 | StartDate = Get-Date; 63 | StartDir = Get-Location; 64 | StartedAt = Get-Date -Format "yyyyMMdd_HHmmss"; 65 | Properties = $Properties; 66 | Credentials = New-Object "System.Collections.Generic.Dictionary[String, System.Management.Automation.PSCredential]"; 67 | Secrets = @{} 68 | } 69 | 70 | $pow.buildFailed = $false 71 | $pow.inBuild = $true 72 | $pow.roles = @{} 73 | 74 | # Get the running version of powerdelivery 75 | if (Get-Module powerdelivery) 76 | { 77 | $pow.version = Get-Module powerdelivery | select version | ForEach-Object { $_.Version.ToString() } 78 | } 79 | else 80 | { 81 | $pow.version = "SOURCE" 82 | } 83 | 84 | Write-Host 85 | Write-Host "PowerDelivery v$($pow.version)" -ForegroundColor $pow.colors['SuccessForeground'] 86 | Write-Host "Target ""$TargetName"" started by ""$($pow.target.RequestedBy)""" 87 | 88 | function LoadRoleScript($role) { 89 | 90 | # Make sure the role script exists 91 | if (!($pow.roles.ContainsKey($role))) { 92 | $rolePath = "$($ProjectName)Delivery\Roles\$role\Always.ps1" 93 | $roleScript = (Join-Path $pow.target.StartDir $rolePath) 94 | if (!(Test-Path $roleScript)) { 95 | Write-Host "Role script $rolePath could not be found." -ForegroundColor Red 96 | throw 97 | }# 98 | 99 | # Run the role script to get the script block 100 | Invoke-Expression -Command ".\$rolePath" 101 | } 102 | } 103 | 104 | # Writes the starting status message for a role 105 | # 106 | function WriteRoleStart($role, $hostOrConnectionURI) { 107 | 108 | Write-Host "[--------- $role -> ($hostOrConnectionURI)" -ForegroundColor $pow.colors['RoleForeground'] 109 | } 110 | 111 | # Invokes a role on a host 112 | # 113 | function InvokeRoleOnHost($nodes, $role, $hostName) { 114 | 115 | LoadRoleScript -role $role 116 | WriteRoleStart -role $role -hostOrConnectionURI $hostName 117 | 118 | InvokeRole -nodes $nodes -role $role -hostName $hostName 119 | } 120 | 121 | # Invokes a role on a connection 122 | function InvokeRoleOnConnection($nodes, $role, $connectionURI) { 123 | 124 | LoadRoleScript -role $role 125 | WriteRoleStart -role $role -hostOrConnectionURI $connectionURI 126 | 127 | InvokeRole -nodes $nodes -role $role -connectionURI $connectionURI 128 | } 129 | 130 | # Invokes a role 131 | # 132 | function InvokeRole($nodes, $role, $hostName, $connectionURI) { 133 | 134 | $hostOrConnectionURI = $hostName 135 | 136 | if ([String]::IsNullOrWhiteSpace($hostName)) { 137 | $hostOrConnectionURI = $connectionURI 138 | } 139 | 140 | # Determine whether to roll up or down 141 | $blockToRun = $pow.roles.Item($role)[0]; 142 | if ($Rollback) { 143 | $blockToRun = $pow.roles.Item($role)[1]; 144 | } 145 | 146 | # Must have a rollback or up block to run 147 | if ($blockToRun) { 148 | 149 | $commandArgs = @{ 150 | ScriptBlock = $blockToRun; 151 | ArgumentList = @($pow.target, $config, $hostOrConnectionURI) 152 | } 153 | 154 | # Check whether a remote node 155 | if ([String]::IsNullOrWhiteSpace($hostName) -or ($hostName.ToLower() -ne 'localhost')) { 156 | 157 | # Set the computer name or connection URI 158 | if ([String]::IsNullOrWhiteSpace($connectionURI)) { 159 | $commandArgs.Add('ComputerName', $hostName) 160 | } 161 | else { 162 | $commandArgs.Add('ConnectionURI', $connectionURI) 163 | if ($nodes.ContainsKey('UseSSL')) { 164 | throw "Role $role cannot set UseSSL and Connection together." 165 | } 166 | } 167 | 168 | $commandArgs.Add('EnableNetworkAccess', 1) 169 | 170 | # Lookup remote credentials if specified 171 | if ($nodes.ContainsKey('Credential')) { 172 | $credentialName = $nodes.Credential 173 | if (!$pow.target.Credentials.ContainsKey($credentialName)) { 174 | throw "Role $role requires credential $credentialName which were not loaded. Are you missing the key file?" 175 | } 176 | else { 177 | Write-Host "Using credentials $credentialName" 178 | $commandArgs.Add('Credential', $pow.target.Credentials.Item($credentialName)) 179 | } 180 | } 181 | 182 | # Add UseSSL if specified 183 | if ($nodes.ContainsKey('UseSSL')) { 184 | $commandArgs.Add('UseSSL', 1); 185 | } 186 | 187 | # Add authentication options if using credentials 188 | if ($commandArgs.ContainsKey('Credential')) { 189 | 190 | $authentication = 'Default' 191 | 192 | # Update authentication of the command if specified on the nodes 193 | if ($nodes.ContainsKey('Authentication')) { 194 | $authentication = $nodes.Authentication 195 | } 196 | 197 | # Set authentication of the command 198 | $commandArgs.Add('Authentication', $authentication) 199 | 200 | # Setup CredSSP if specified 201 | if ($authentication.ToLower() -eq 'credssp') { 202 | 203 | $trustedHost = $hostName 204 | 205 | # Verify that a host was specified 206 | if ([String]::IsNullOrWhiteSpace($hostName)) { 207 | $uri = New-Object System.Uri -ArgumentList $connectionURI 208 | $trustedHost = $uri.Host 209 | } 210 | 211 | $credSSP = Get-WSManCredSSP 212 | $nodeExists = $false 213 | 214 | # Check whether remote node exists in trusted hosts 215 | if ($credSSP -ne $null) { 216 | if ($credSSP.length -gt 0) { 217 | $trustedClients = $credSSP[0].Substring($credSSP[0].IndexOf(":") + 2) 218 | $trustedClientsList = $trustedClients -split "," | % { $_.Trim().ToLower() } 219 | if ($trustedClientsList.Contains("wsman/$($trustedHost.ToLower())")) { 220 | Write-host """$($trustedHost)"" is trusted for CredSSP delegation." 221 | $nodeExists = $true 222 | } 223 | } 224 | } 225 | 226 | # Enable CredSSP to remote node if not found in trusted hosts 227 | if (!$nodeExists) { 228 | Write-Host "Enabling CredSSP delegation to ""$($trustedHost)""..." 229 | Enable-WSManCredSSP -Role Client -DelegateComputer $trustedHost -Force | Out-Null 230 | } 231 | } 232 | } 233 | } 234 | 235 | # Run the role 236 | try { 237 | Invoke-Command @commandArgs 238 | } 239 | catch { 240 | throw "Error in role ""$role"" on $hostOrConnectionURI" + [Environment]::NewLine, $_ 241 | } 242 | 243 | Set-Location $pow.target.StartDir 244 | } 245 | } 246 | 247 | try { 248 | if ($Rollback) { 249 | Write-Host "Rolling back ""$ProjectName"" in ""$EnvironmentName"" environment..." 250 | } 251 | else { 252 | Write-Host "Delivering ""$ProjectName"" to ""$EnvironmentName"" environment..." 253 | } 254 | Write-Host 255 | 256 | $myDocumentsFolder = [Environment]::GetFolderPath("MyDocuments") 257 | 258 | # Test for secrets 259 | $secretsPath = "$($ProjectName)Delivery\Secrets" 260 | if (Test-Path $secretsPath) { 261 | 262 | # Iterate secret key directories 263 | foreach ($keyDirectory in (Get-ChildItem -Directory $secretsPath)) { 264 | 265 | # Try to get the key 266 | $keyBytes = GetKeyBytes -ProjectDir "$($ProjectName)Delivery" -KeyName $keyDirectory 267 | 268 | if ($keyBytes -ne $null) { 269 | 270 | # Iterate secrets 271 | foreach ($secretFile in (Get-ChildItem -File -Filter *.secret $keyDirectory.FullName)) { 272 | 273 | $secretName = [IO.Path]::GetFileNameWithoutExtension($secretFile) 274 | $secretFullPath = Join-Path $keyDirectory $secretFile 275 | 276 | # Try to decrypt the secret 277 | $secret = $null 278 | try { 279 | $secret = Get-Content $secretFullPath | ConvertTo-SecureString -Key $keyBytes 280 | } 281 | catch { 282 | throw "Couldn't decrypt $secretFullPath with key $keyDirectory - $_" 283 | } 284 | 285 | # Add secret to hash in target 286 | $pow.target.Secrets.Add($secretName, $secret) 287 | } 288 | 289 | # Test for credentials 290 | $credsPath = Join-Path $keyDirectory.FullName Credentials 291 | if (Test-Path $credsPath) { 292 | 293 | # Iterate credentials 294 | foreach ($credentialsFile in (Get-ChildItem -File -Filter *.credential $credsPath)) { 295 | 296 | $credsFullPath = Join-Path $credsPath $credentialsFile 297 | 298 | # Try to decrypt the password 299 | $password = $null 300 | try { 301 | $password = Get-Content $credsFullPath | ConvertTo-SecureString -Key $keyBytes 302 | } 303 | catch { 304 | throw "Couldn't decrypt $credsFullPath with key $keyDirectory - $_" 305 | } 306 | 307 | # Fix up the username 308 | $credsFileName = [IO.Path]::GetFileNameWithoutExtension($credentialsFile) 309 | $userName = $credsFileName -replace '#', '\' 310 | 311 | # Create the PowerShell credential 312 | $userCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $userName, $password 313 | 314 | # Add credentials to hash in target 315 | $pow.target.Credentials.Add($userName, [PSCredential]$userCredential) 316 | } 317 | } 318 | } 319 | } 320 | } 321 | 322 | # Test for shared configuration 323 | $sharedConfigPath = "$($ProjectName)Delivery\Configuration\_Shared.ps1" 324 | $sharedConfigScript = (Join-Path $pow.target.StartDir $sharedConfigPath) 325 | if (!(Test-Path $sharedConfigScript)) { 326 | Write-Host "Shared configuration script $sharedConfigPath could not be found." -ForegroundColor Red 327 | throw 328 | } 329 | 330 | # Load shared configuration 331 | try { 332 | $pow.sharedConfig = Invoke-Command -ComputerName localhost -File $sharedConfigScript -ArgumentList $pow.target 333 | } 334 | catch { 335 | Write-Host "Error occurred loading $sharedConfigPath." -ForegroundColor Red 336 | throw 337 | } 338 | 339 | # Test for environment configuration 340 | $envConfigPath = "$($ProjectName)Delivery\Configuration\$EnvironmentName.ps1" 341 | $envConfigScript = (Join-Path $pow.target.StartDir $envConfigPath) 342 | if (!(Test-Path $envConfigScript)) { 343 | Write-Host "Environment configuration script $envConfigPath could not be found." -ForegroundColor Red 344 | throw 345 | } 346 | 347 | # Load environment configuration 348 | try { 349 | $pow.envConfig = Invoke-Command -ComputerName localhost -File $envConfigScript -ArgumentList @($pow.target, $pow.sharedConfig) 350 | } 351 | catch { 352 | Write-Host "Error occurred loading $envConfigPath." -ForegroundColor Red 353 | throw 354 | } 355 | 356 | $config = @{} 357 | 358 | # Add environment-specific config settings 359 | foreach ($envConfigSetting in $pow.envConfig.GetEnumerator()) { 360 | $config.Add($envConfigSetting.Key, $envConfigSetting.Value) 361 | } 362 | 363 | # Add shared config settings 364 | foreach ($sharedConfigSetting in $pow.sharedConfig.GetEnumerator()) { 365 | if (!($config.ContainsKey($sharedConfigSetting.Key))) { 366 | $config.Add($sharedConfigSetting.Key, $sharedConfigSetting.Value) 367 | } 368 | } 369 | 370 | # Test for environment 371 | $envPath = "$($ProjectName)Delivery\Environments\$EnvironmentName.ps1" 372 | $envScript = (Join-Path $pow.target.StartDir $envPath) 373 | if (!(Test-Path $envScript)) { 374 | Write-Host "Environment script $envPath could not be found." -ForegroundColor Red 375 | throw 376 | } 377 | 378 | # Load environment 379 | try { 380 | $pow.target.Environment = Invoke-Command -ComputerName localhost -File $envScript -ArgumentList @($pow.target, $config) 381 | } 382 | catch { 383 | Write-Host "Error occurred loading $envPath." -ForegroundColor Red 384 | throw 385 | } 386 | 387 | # Test for target 388 | $targetPath = "$($ProjectName)Delivery\Targets\$TargetName.ps1" 389 | $targetScript = (Join-Path $pow.target.StartDir $targetPath) 390 | if (!(Test-Path $targetScript)) { 391 | Write-Host "Target script $targetPath could not be found." -ForegroundColor Red 392 | throw 393 | } 394 | 395 | # Load target 396 | try { 397 | $pow.targetScript = Invoke-Expression -Command $targetScript 398 | } 399 | catch { 400 | Write-Host "Error occurred loading $targetPath." -ForegroundColor Red 401 | throw 402 | } 403 | 404 | # Iterate steps of the target 405 | foreach ($targetStep in $pow.targetScript.GetEnumerator()) { 406 | Write-Host "[----- $($targetStep.Key)" -ForegroundColor $pow.colors['StepForeground'] 407 | 408 | # Iterate sets of nodes in the step 409 | foreach ($node in $targetStep.Value.Nodes) { 410 | 411 | # Make sure the environment contains the nodes 412 | if (!$pow.target.Environment.ContainsKey($node)) { 413 | Write-Host "Step $($targetStep.Key) of target $TargetName refers to nodeset $node not found in $EnvironmentName environment." -ForegroundColor Red 414 | throw 415 | } 416 | 417 | # Get the current set of nodes for the target 418 | $nodes = $pow.target.Environment.Item($node) 419 | 420 | # Make sure they didn't specify hosts and connections together 421 | if ($nodes.ContainsKey('Hosts') -and $nodes.ContainsKey('Connections')) { 422 | throw "Nodes $node cannot combine Hosts and Connections." 423 | } 424 | 425 | # Iterate hosts in the set 426 | if ($nodes.ContainsKey('Hosts')) { 427 | foreach ($hostName in $nodes.Hosts) { 428 | # Iterate roles 429 | foreach ($role in $targetStep.Value.Roles) { 430 | InvokeRoleOnHost -nodes $nodes -role $role -hostName $hostName 431 | } 432 | } 433 | } 434 | # Iterate connections in the set 435 | elseif ($nodes.ContainsKey('Connections')) { 436 | # Iterate connections in the set 437 | foreach ($connectionURI in $nodes.Connections) { 438 | # Iterate roles 439 | foreach ($role in $targetStep.Value.Roles) { 440 | InvokeRoleOnConnection -nodes $nodes -role $role -connectionURI $connectionURI 441 | } 442 | } 443 | } 444 | } 445 | } 446 | } 447 | catch { 448 | $pow.buildFailed = $true 449 | throw 450 | } 451 | finally { 452 | $build_time = New-Timespan -Start ($pow.target.StartDate) -End (Get-Date) 453 | $build_time_string = '' 454 | 455 | $build_time_days = $build_time.Days 456 | if ($build_time_days -gt 0) { 457 | $build_time_string += "$build_time_days days" 458 | } 459 | 460 | $build_time_hours = $build_time.Hours 461 | if ($build_time_hours -gt 0) { 462 | if ($build_time_string.Length -gt 0) { 463 | $build_time_string += ' ' 464 | } 465 | $build_time_string += "$build_time_hours hrs" 466 | } 467 | 468 | $build_time_minutes = $build_time.Minutes 469 | if ($build_time_minutes -gt 0) { 470 | if ($build_time_string.Length -gt 0) { 471 | $build_time_string += ' ' 472 | } 473 | $build_time_string += "$build_time_minutes min" 474 | } 475 | 476 | $build_time_seconds = $build_time.Seconds 477 | if ($build_time_seconds -gt 0) { 478 | if ($build_time_string.Length -gt 0) { 479 | $build_time_string += ' ' 480 | } 481 | $build_time_string += "$build_time_seconds sec" 482 | } 483 | 484 | $build_time_ms = $build_time.Milliseconds 485 | if ($build_time_ms -gt 0) { 486 | if ($build_time_string.Length -gt 0) { 487 | $build_time_string += ' ' 488 | } 489 | $build_time_string += "$build_time_ms ms" 490 | } 491 | 492 | Write-Host 493 | 494 | if ($pow.buildFailed) { 495 | Write-Host "Target ""$TargetName"" failed in $build_time_string." -ForegroundColor $pow.colors['FailureForeground'] 496 | } 497 | else { 498 | Write-Host "Target ""$TargetName"" succeeded in $build_time_string." -ForegroundColor $pow.colors['SuccessForeground'] 499 | } 500 | 501 | Set-Location $pow.target.StartDir | Out-Null 502 | } 503 | } 504 | 505 | Export-ModuleMember -Function Start-Delivery 506 | -------------------------------------------------------------------------------- /Modules/PowerDelivery/Cmdlets/Write-RelativePath.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Returns an absolute path relative to the current directory. 4 | 5 | .Description 6 | Returns an absolute path relative to the current directory. 7 | 8 | .Example 9 | Write-RelativePath "C:\SomeDir\SomeOtherDir" 10 | 11 | .Parameter Path 12 | The absolute path to return the relative form of. 13 | #> 14 | function Write-RelativePath { 15 | [CmdletBinding()] 16 | param ( 17 | [Parameter(Position=0,Mandatory=0)][string] $path 18 | ) 19 | 20 | $pathUri = New-Object -TypeName System.Uri -ArgumentList $path 21 | 22 | if ($pathUri.IsUnc) { 23 | $path 24 | } 25 | else { 26 | if (-not ("win32.Shell" -as [type])) { 27 | Add-Type -Namespace win32 -Name Shell -MemberDefinition @" 28 | [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)] 29 | public static extern bool PathRelativePathTo(System.Text.StringBuilder lpszDst, 30 | string From, System.IO.FileAttributes attrFrom, String to, System.IO.FileAttributes attrTo); 31 | "@ 32 | } 33 | 34 | $fullPath = [System.IO.Path]::GetFullPath($path) 35 | 36 | $pathBldr = New-Object -TypeName System.Text.StringBuilder 260 37 | 38 | $startDir = Get-Location 39 | 40 | if ($powerdelivery.inBuild) { 41 | $startDir = $pow.target.StartDir 42 | } 43 | 44 | $result = [win32.Shell]::PathRelativePathTo($pathBldr, $startDir, [System.IO.FileAttributes]::Normal, $fullPath, [System.IO.FileAttributes]::Normal) 45 | if ($result) { 46 | $pathBldr.ToString() 47 | } 48 | else { 49 | "NOTFOUND" 50 | } 51 | } 52 | } 53 | 54 | Export-ModuleMember -Function Write-RelativePath -------------------------------------------------------------------------------- /Modules/PowerDelivery/PowerDelivery.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavonius/powerdelivery/1d284f60b319e6ef6ab60a6a9901bcd3ebb7c889/Modules/PowerDelivery/PowerDelivery.psd1 -------------------------------------------------------------------------------- /Modules/PowerDelivery/PowerDelivery.psm1: -------------------------------------------------------------------------------- 1 | <# PowerDelivery.psm1 2 | 3 | Script for PowerShell module. 4 | 5 | http://www.powerdelivery.io 6 | #> 7 | 8 | function GetProjectDirectory { 9 | if (!((Test-Path 'Targets') -and (Test-Path 'Environments') -and (Test-Path 'Roles'))) { 10 | throw "This command must be run from within a powerdelivery project directory." 11 | } 12 | Get-Location 13 | } 14 | 15 | function ValidateNewFileName($Type, $FileName) { 16 | $isValidName = "^[a-zA-Z0-9@#_\.\-]+$" 17 | if (!($FileName -match $isValidName)) { 18 | throw "Please use a $Type that only includes alphanumeric characters, @ sign, pound sign, underscore, period or dash, and no spaces." 19 | } 20 | } 21 | 22 | function GetMyDocumentsFolder { 23 | [Environment]::GetFolderPath("MyDocuments") 24 | } 25 | 26 | function GetKeyBytes($ProjectDir, $KeyName, [switch]$ThrowOnError) { 27 | $myDocumentsFolder = GetMyDocumentsFolder 28 | $keysFolderPath = Join-Path $myDocumentsFolder "PowerDelivery\Keys\$ProjectDir" 29 | $keyFilePath = Join-Path $keysFolderPath "$KeyName.key" 30 | 31 | if (!(Test-Path $keyFilePath)) { 32 | if ($ThrowOnError) { 33 | throw "Key not found at $keyFilePath." 34 | } 35 | else { 36 | return $null 37 | } 38 | } 39 | $keyString = Get-Content $keyFilePath 40 | try { 41 | [Convert]::FromBase64String($keyString) 42 | } 43 | catch { 44 | throw "Key $KeyName is invalid - $_" 45 | } 46 | } 47 | 48 | function GetNewKey { 49 | $key = New-Object Byte[] 32 50 | [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($key) 51 | [Convert]::ToBase64String($key) 52 | } 53 | 54 | # Load cmdlets 55 | $cmdletsDir = (Join-Path $PSScriptRoot "Cmdlets") 56 | gci $cmdletsDir -Filter "*.ps1" | ForEach-Object { . (Join-Path $cmdletsDir $_.Name) } 57 | 58 | $env:TERM = "msys" 59 | 60 | $script:pow = @{} 61 | $pow.scriptDir = Split-Path $MyInvocation.MyCommand.Path 62 | -------------------------------------------------------------------------------- /Modules/PowerDelivery/TODO.txt: -------------------------------------------------------------------------------- 1 | * Role migrations 2 | -------------------------------------------------------------------------------- /Modules/PowerDelivery/Templates/Configuration.ps1.template: -------------------------------------------------------------------------------- 1 | <# See http://www.powerdelivery.io/variables.html #> 2 | param($target, $shared) 3 | @{ 4 | # Define hash values with environment-specific variables here 5 | } 6 | -------------------------------------------------------------------------------- /Modules/PowerDelivery/Templates/Environment.ps1.template: -------------------------------------------------------------------------------- 1 | <# See http://www.powerdelivery.io/environments.html #> 2 | param($target, $config) 3 | @{ 4 | # Define sets of nodes here 5 | } 6 | -------------------------------------------------------------------------------- /Modules/PowerDelivery/Templates/Role.ps1.template: -------------------------------------------------------------------------------- 1 | <# See http://www.powerdelivery.io/roles.html #> 2 | Delivery:Role -Up { 3 | param($target, $config, $node) 4 | 5 | # Write PowerShell deployment logic here 6 | } -Down { 7 | param($target, $config, $node) 8 | 9 | # Write PowerShell rollback logic here (if necessary) 10 | } 11 | -------------------------------------------------------------------------------- /Modules/PowerDelivery/Templates/Target.ps1.template: -------------------------------------------------------------------------------- 1 | <# See http://www.powerdelivery.io/targets.html #> 2 | [ordered]@{ 3 | # Define the steps in your target here 4 | } 5 | -------------------------------------------------------------------------------- /Modules/PowerDelivery/Templates/_Shared.ps1.template: -------------------------------------------------------------------------------- 1 | <# See http://www.powerdelivery.io/variables.html #> 2 | param($target) 3 | @{ 4 | # Define hash values with shared variables here 5 | } 6 | -------------------------------------------------------------------------------- /Modules/PowerDeliveryNode/Cmdlets/Get-DeliveryFilesFromAzure.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Downloads files that were published by powerdelivery to Windows Azure 4 | during the current run of a target onto a node. 5 | 6 | .Description 7 | Downloads files that were published by powerdelivery to Windows Azure 8 | during the current run of a target onto a node. 9 | 10 | .Example 11 | Delivery:Role { 12 | param($target, $config, $node) 13 | 14 | # You must install PowerDeliveryNode using chocolatey in a 15 | # role that has run before this one on the remote node first. 16 | Import-Module PowerDeliveryNode 17 | 18 | # $releasePath will be C:\Users\\AppData\Roaming\\Current 19 | # pointing to a yyyyMMdd_HHmmss folder in the same directory. 20 | $releasePath = New-DeliveryReleasePath $target [Environment]::GetFolderPath("AppData") 21 | 22 | # Downloads files within the folder "MyApp" that were uploaded to Azure 23 | # into a local directory for the release on the node created above. 24 | Get-DeliveryFilesFromAzure -Target $target ` 25 | -Path "MyApp" ` 26 | -Destination $releasePath ` 27 | -Credential $target.Credentials['admin@myazuredomain.com'] ` 28 | -SubscriptionId $config.MyAzureSubsciptionId ` 29 | -StorageAccountName $config.MyAzureStorageAccountName ` 30 | -StorageAccountKey $config.MyAzureStorageAccountKey ` 31 | -StorageContainer $config.MyAzureStorageContainer 32 | } 33 | 34 | .Parameter Target 35 | The $target parameter from the role. 36 | 37 | .Parameter Path 38 | The path of files to download relative to the release directory (/) 39 | uploaded to Azure with the Publish-DeliveryFilesToAzure cmdlet. 40 | 41 | .Parameter Destination 42 | The directory in which to place downloaded files. The New-DeliveryReleasePath cmdlet 43 | is recommended to enable rollback via the Undo-DeliveryReleasePath cmdlet in a Down block. 44 | 45 | .Parameter Credential 46 | The Windows Azure account credentials to use. You must configure the computer running powerdelivery 47 | as described at http://www.powerdelivery.io/secrets.html#using_credentials_in_remote_roles 48 | for these to travel from to the node that will download files with this cmdlet. 49 | 50 | .Parameter SubscriptionId 51 | A Windows Azure subscription that the account in the Credential parameter is permitted 52 | to use. 53 | 54 | .Parameter StorageAccountName 55 | A Windows Azure storage account that the account in the Credential parameter is permitted 56 | to access. 57 | 58 | .Parameter StorageAccountKey 59 | A Windows Azure storage account key that matches the StorageAccountName parameter providing 60 | read access. 61 | 62 | .Parameter StorageContainer 63 | A container within the Windows Azure storage account referred to in the StorageAccountName 64 | parameter that contains files uploaded with the Publish-DeliveryFilesToAzure cmdlet in 65 | a prior role that ran on localhost to create a release. 66 | #> 67 | function Get-DeliveryFilesFromAzure { 68 | param( 69 | [Parameter(Position=0,Mandatory=1)][hashtable] $Target, 70 | [Parameter(Position=1,Mandatory=1)][string] $Path, 71 | [Parameter(Position=2,Mandatory=1)][string] $Destination, 72 | [Parameter(Position=3,Mandatory=1)][PSCredential] $Credential, 73 | [Parameter(Position=4,Mandatory=1)][string] $SubscriptionId, 74 | [Parameter(Position=5,Mandatory=1)][string] $StorageAccountName, 75 | [Parameter(Position=6,Mandatory=1)][string] $StorageAccountKey, 76 | [Parameter(Position=7,Mandatory=1)][string] $StorageContainer 77 | ) 78 | 79 | $verbose = Test-Verbose 80 | 81 | if (-not ("win32.Shell" -as [type])) { 82 | Add-Type -Namespace win32 -Name Shell -MemberDefinition @" 83 | [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)] 84 | public static extern bool PathRelativePathTo(System.Text.StringBuilder lpszDst, 85 | string From, System.IO.FileAttributes attrFrom, String to, System.IO.FileAttributes attrTo); 86 | "@ 87 | } 88 | 89 | Import-Module Azure 90 | 91 | # Set the active Azure account 92 | Add-AzureAccount -Credential $Credential | Out-Null 93 | 94 | if ($verbose) { 95 | Write-Host "Using Azure subscription ""$SubscriptionId""" 96 | } 97 | 98 | # Set the active subscription 99 | Select-AzureSubscription -SubscriptionId $SubscriptionId 100 | 101 | if ($verbose) { 102 | Write-Host "Using Azure storage account ""$StorageAccountName""" 103 | } 104 | 105 | # Connect to the Azure storage account 106 | $storageContext = New-AzureStorageContext -StorageAccountName $StorageAccountName ` 107 | -StorageAccountKey $StorageAccountKey 108 | 109 | # Get the powerdelivery share 110 | $releasesContainer = Get-AzureStorageContainer -Name $StorageContainer ` 111 | -Context $storageContext ` 112 | -ErrorAction SilentlyContinue 113 | if (!$releasesContainer) { 114 | throw "Azure storage container $StorageContainer not found in account $StorageAccountName." 115 | } 116 | 117 | # List all files in the container for all releases 118 | $allReleaseFiles = Get-AzureStorageBlob -Blob "$($target.ProjectName)*" ` 119 | -Container $StorageContainer ` 120 | -Context $storageContext 121 | 122 | $releasePrefix = "$($target.ProjectName)/$($target.StartedAt)" 123 | $pathToGet = $releasePrefix 124 | 125 | # Append the source path if not the entire directory 126 | if ($Path -ne ".") { 127 | $extraPath = $Path -replace '\\', '/' 128 | 129 | if ($extraPath.StartsWith("/")) { 130 | $extraPath = $extraPath.Substring(1) 131 | } 132 | 133 | $pathToGet = "$releasePrefix/$extraPath" 134 | } 135 | 136 | # Iterate the files in the release 137 | foreach ($releaseFile in $allReleaseFiles) { 138 | 139 | # Only download files for the current release 140 | if ($releaseFile.Name.StartsWith($pathToGet)) { 141 | 142 | # Fix up the filename to exclude the release and timestamp 143 | $targetPath = $releaseFile.Name.Substring($releasePrefix.Length + 1) 144 | $targetPath = $targetPath -replace '/', '\' 145 | $targetPath = Join-Path $Destination $targetPath 146 | 147 | # Create the directory containing the file if it doesn't exist 148 | $targetDir = [IO.Path]::GetDirectoryName($targetPath) 149 | if (!(Test-Path $targetDir)) { 150 | New-Item -ItemType Directory $targetDir | Out-Null 151 | } 152 | 153 | # Download the file 154 | Get-AzureStorageBlobContent -Blob $releaseFile.Name ` 155 | -Container $StorageContainer ` 156 | -Context $storageContext ` 157 | -Destination $targetPath | Out-Null 158 | } 159 | } 160 | } 161 | 162 | Export-ModuleMember -Function Get-DeliveryFilesFromAzure 163 | -------------------------------------------------------------------------------- /Modules/PowerDeliveryNode/Cmdlets/Get-DeliveryFilesFromS3.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Downloads files that were published by powerdelivery to an AWS Simple Storage Service bucket 4 | during the current run of a target onto a node. 5 | 6 | .Description 7 | Downloads files that were published by powerdelivery to an AWS Simple Storage Service bucket 8 | during the current run of a target onto a node. 9 | 10 | .Example 11 | Delivery:Role { 12 | param($target, $config, $node) 13 | 14 | # You must install PowerDeliveryNode using chocolatey in a 15 | # role that has run before this one on the remote node first. 16 | Import-Module PowerDeliveryNode 17 | 18 | # $releasePath will be C:\Users\\AppData\Roaming\\Current 19 | # pointing to a yyyyMMdd_HHmmss folder in the same directory. 20 | $releasePath = New-DeliveryReleasePath $target [Environment]::GetFolderPath("AppData") 21 | 22 | # Downloads files within the folder "MyApp" that were uploaded to S3 23 | # into a local directory for the release on the node created above. 24 | Get-DeliveryFilesFromS3 -Target $target ` 25 | -Path "MyApp" ` 26 | -Destination $releasePath ` 27 | -ProfileName "MyProfile" ` 28 | -BucketName "MyAppReleases" 29 | } 30 | 31 | .Parameter Target 32 | The $target parameter from the role. 33 | 34 | .Parameter Path 35 | The path of files to download relative to the release directory (/) 36 | uploaded to S3 with the Publish-DeliveryFilesToS3 cmdlet. 37 | 38 | .Parameter Destination 39 | The directory in which to place downloaded files. The New-DeliveryReleasePath cmdlet 40 | is recommended to enable rollback via the Undo-DeliveryReleasePath cmdlet in a Down block. 41 | 42 | .Parameter ProfileName 43 | The name of the AWS profile containing credentials to use. 44 | See https://docs.aws.amazon.com/powershell/latest/userguide/specifying-your-aws-credentials.html 45 | 46 | .Parameter BucketName 47 | The name of the S3 bucket that contains files uploaded with the Publish-DeliveryFilesToS3 cmdlet in 48 | a prior role that ran on localhost to create a release. 49 | 50 | .Parameter ProfilesLocation 51 | The location to look in for the AWS profile containing credentials. 52 | See https://docs.aws.amazon.com/powershell/latest/userguide/specifying-your-aws-credentials.html 53 | #> 54 | function Get-DeliveryFilesFromS3 { 55 | param( 56 | [Parameter(Position=0,Mandatory=1)][hashtable] $Target, 57 | [Parameter(Position=1,Mandatory=1)][string] $Path, 58 | [Parameter(Position=2,Mandatory=1)][string] $Destination, 59 | [Parameter(Position=3,Mandatory=1)][string] $ProfileName, 60 | [Parameter(Position=4,Mandatory=1)][string] $BucketName, 61 | [Parameter(Position=5,Mandatory=0)][string] $ProfilesLocation 62 | ) 63 | 64 | $verbose = Test-Verbose 65 | 66 | if (-not ("win32.Shell" -as [type])) { 67 | Add-Type -Namespace win32 -Name Shell -MemberDefinition @" 68 | [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)] 69 | public static extern bool PathRelativePathTo(System.Text.StringBuilder lpszDst, 70 | string From, System.IO.FileAttributes attrFrom, String to, System.IO.FileAttributes attrTo); 71 | "@ 72 | } 73 | 74 | $setAwsCredentialsArgs = @{ 75 | ProfileName = $ProfileName 76 | } 77 | 78 | if (![String]::IsNullOrEmpty($ProfilesLocation)) { 79 | $setAwsCredentialsArgs.Add("ProfilesLocation", $ProfilesLocation) 80 | } 81 | 82 | # Set the active AWS credentials 83 | Set-AwsCredentials @$setAwsCredentialsArgs 84 | 85 | if ($verbose) { 86 | Write-Host "Using AWS profile ""$ProfileName""" 87 | } 88 | 89 | # Connect to the S3 bucket 90 | $bucket = Get-S3Bucket $BucketName 91 | if (!$bucket) { 92 | throw "S3 bucket $BucketName not found." 93 | } 94 | 95 | # List all files in the container for all releases 96 | $allReleaseFiles = Get-S3Object -BucketName $BucketName ` 97 | -KeyPrefix $target.ProjectName 98 | 99 | $releasePrefix = "$($target.ProjectName)/$($target.StartedAt)" 100 | $pathToGet = $releasePrefix 101 | 102 | # Append the source path if not the entire directory 103 | if ($Path -ne ".") { 104 | $extraPath = $Path -replace '\\', '/' 105 | 106 | if ($extraPath.StartsWith("/")) { 107 | $extraPath = $extraPath.Substring(1) 108 | } 109 | 110 | $pathToGet = "$releasePrefix/$extraPath" 111 | } 112 | 113 | # Iterate the files in the release 114 | foreach ($releaseFile in $allReleaseFiles) { 115 | 116 | # Only download files for the current release 117 | if ($releaseFile.Key.StartsWith($pathToGet)) { 118 | 119 | # Fix up the filename to exclude the release and timestamp 120 | $targetPath = $releaseFile.Key.Substring($releasePrefix.Length + 1) 121 | $targetPath = $targetPath -replace '/', '\' 122 | $targetPath = Join-Path $Destination $targetPath 123 | 124 | # Create the directory containing the file if it doesn't exist 125 | $targetDir = [IO.Path]::GetDirectoryName($targetPath) 126 | if (!(Test-Path $targetDir)) { 127 | New-Item -ItemType Directory $targetDir | Out-Null 128 | } 129 | 130 | # Download the file 131 | Read-S3Object -BucketName $BucketName ` 132 | -Key $releaseFile.Key ` 133 | -File $targetPath | Out-Null 134 | } 135 | } 136 | } 137 | 138 | Export-ModuleMember -Function Get-DeliveryFilesFromS3 139 | -------------------------------------------------------------------------------- /Modules/PowerDeliveryNode/Cmdlets/New-DeliveryReleasePath.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Creates a new release directory on a remote node being deployed to with powerdelivery. 4 | 5 | .Description 6 | Creates a new release directory on a remote node being deployed to with powerdelivery. 7 | A directory will be created within the path you specify and named after the ProjectName 8 | property of the target. A directory will be created within it with the timestamp of the 9 | current run of the target and symbolicly linked to "Current". 10 | 11 | Any releases older than the number in the Keep parameter will be deleted. 12 | 13 | .Example 14 | Delivery:Role -Up { 15 | param($target, $config, $node) 16 | 17 | # You must install PowerDeliveryNode using chocolatey in a 18 | # role that has run before this one on the remote node first. 19 | Import-Module PowerDeliveryNode 20 | 21 | # $releasePath will be C:\Users\\AppData\Roaming\\Current 22 | # pointing to a yyyyMMdd_HHmmss folder in the same directory. 23 | $releasePath = New-DeliveryReleasePath $target [Environment]::GetFolderPath("AppData") 24 | } -Down { 25 | 26 | # You must install PowerDeliveryNode using chocolatey in a 27 | # role that has run before this one on the remote node first. 28 | Import-Module PowerDeliveryNode 29 | 30 | # This will rollback a previous release. If no previous 31 | # release exists it will be the same path as current. 32 | $releasePath = Undo-DeliveryReleasePath $target [Environment]::GetFolderPath("AppData") 33 | } 34 | 35 | The release will be created at the path: 36 | 37 | C:\Users\\AppData\Roaming\\ 38 | 39 | And symlinked to: 40 | 41 | C:\Users\\AppData\Roaming\\Current 42 | 43 | .Parameter Target 44 | The hash of target properties for the powerdelivery run. 45 | 46 | .Parameter Path 47 | The parent path into which to create the release path. 48 | 49 | .Parameter Keep 50 | The number of previous releases to keep. Defaults to 5. 51 | #> 52 | function New-DeliveryReleasePath { 53 | [CmdletBinding()] 54 | param( 55 | [Parameter(Position=0,Mandatory=1)][hashtable] $Target, 56 | [Parameter(Position=1,Mandatory=1)][string] $Path, 57 | [Parameter(Position=2,Mandatory=0)][int] $Keep = 5 58 | ) 59 | 60 | # Reference a sub-directory named after the project 61 | $projectPath = Join-Path $Path $target.ProjectName 62 | 63 | # Create a directory for this release 64 | $currentReleasePath = Join-Path $projectPath "Current" 65 | $thisReleasePath = Join-Path $projectPath $target.StartedAt 66 | 67 | if (!(Test-Path $thisReleasePath)) { 68 | New-Item $thisReleasePath -ItemType Directory | Out-Null 69 | 70 | # Remove old link to current release 71 | if (Test-Path $currentReleasePath) { 72 | & cmd /c "rmdir ""$currentReleasePath""" 73 | } 74 | 75 | # Link this release to the current release 76 | & cmd /c "mklink /J ""$currentReleasePath"" ""$thisReleasePath""" | Out-Null 77 | 78 | # Get releases 79 | $releases = Get-ChildItem -Directory $projectPath -Exclude "Current" 80 | 81 | # Delete releases older than the last 5 82 | if ($releases.count -gt $Keep) { 83 | $oldReleaseCount = $releases.count - $Keep 84 | $releases | 85 | Sort-Object -Property Name | 86 | Select -First $oldReleaseCount | 87 | Remove-Item -Force -Recurse | Out-Null 88 | } 89 | } 90 | 91 | $currentReleasePath 92 | } 93 | 94 | Export-ModuleMember -Function New-DeliveryReleasePath 95 | -------------------------------------------------------------------------------- /Modules/PowerDeliveryNode/Cmdlets/Test-CommandExists.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Tests whether a command is present in the PowerShell path. 4 | 5 | .Description 6 | Tests whether a command is present in the PowerShell path. Useful for checking for dependencies. 7 | 8 | .Example 9 | Test-Command choco 10 | 11 | .Parameter CommandName 12 | The name of the command to test for. 13 | #> 14 | function Test-CommandExists { 15 | [CmdletBinding()] 16 | param( 17 | [Parameter(Position=0,Mandatory=1)][string] $CommandName 18 | ) 19 | 20 | $oldPreference = $ErrorActionPreference 21 | $ErrorActionPreference = 'stop' 22 | 23 | try { 24 | if (Get-Command $CommandName) { 25 | $true 26 | } 27 | } 28 | catch { 29 | $false 30 | } 31 | finally { 32 | $ErrorActionPreference = $oldPreference 33 | } 34 | } 35 | 36 | Export-ModuleMember -Function Test-CommandExists 37 | -------------------------------------------------------------------------------- /Modules/PowerDeliveryNode/Cmdlets/Test-Verbose.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Tests whether PowerShell is being run in verbose mode. 4 | 5 | .Description 6 | Tests whether PowerShell is being run in verbose mode. Useful for logging additional details only when requested. 7 | 8 | .Example 9 | if (Test-Verbose) { 10 | Write-Host "Detailed log entry" 11 | } 12 | #> 13 | function Test-Verbose { 14 | [CmdletBinding()] 15 | param() 16 | [System.Management.Automation.ActionPreference]::SilentlyContinue -ne $VerbosePreference 17 | } 18 | 19 | Export-ModuleMember -Function Test-Verbose 20 | -------------------------------------------------------------------------------- /Modules/PowerDeliveryNode/Cmdlets/Undo-DeliveryReleasePath.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Rolls back to a previous release directory on a remote node being deployed to with powerdelivery. 4 | 5 | .Description 6 | Rolls back to a previous release directory on a remote node being deployed to with powerdelivery. 7 | Modifies the symbolic link pointing to the current release path to point to the previous release 8 | and deletes the old current release directory. If no previous release exists, will leave the 9 | current release as is and return it. 10 | 11 | .Example 12 | Delivery:Role -Up { 13 | param($target, $config, $node) 14 | 15 | # You must install PowerDeliveryNode using chocolatey in a 16 | # role that has run before this one on the remote node first. 17 | Import-Module PowerDeliveryNode 18 | 19 | # $releasePath will be C:\Users\\AppData\Roaming\\Current 20 | $releasePath = New-DeliveryReleasePath $target [Environment]::GetFolderPath("AppData") 21 | } -Down { 22 | 23 | # You must install PowerDeliveryNode using chocolatey in a 24 | # role that has run before this one on the remote node first. 25 | Import-Module PowerDeliveryNode 26 | 27 | # This will rollback a previous release. If no previous 28 | # release exists it will be the same path as current. 29 | $releasePath = Undo-DeliveryReleasePath $target [Environment]::GetFolderPath("AppData") 30 | } 31 | 32 | The releases will be looked for in the path: 33 | 34 | C:\Users\\AppData\Roaming\ 35 | 36 | And the previous release symlinked to: 37 | 38 | C:\Users\\AppData\Roaming\\Current 39 | 40 | .Parameter Target 41 | The hash of target properties for the powerdelivery run. 42 | 43 | .Parameter Path 44 | The parent path in which to look for releases. 45 | #> 46 | function Undo-DeliveryReleasePath { 47 | [CmdletBinding()] 48 | param( 49 | [Parameter(Position=0,Mandatory=1)][hashtable] $Target, 50 | [Parameter(Position=1,Mandatory=1)][string] $Path 51 | ) 52 | 53 | $previousReleasePath = $null 54 | 55 | # Reference a sub-directory named after the project 56 | $projectPath = Join-Path $Path $target.ProjectName 57 | 58 | # If at least one release has occurred 59 | if (Test-Path $projectPath) { 60 | 61 | # Get current and previous release 62 | $lastRelease = Get-ChildItem -Directory $projectPath -Exclude "Current" | 63 | Sort-Object -Descending -Property Name | Select -First 2 64 | 65 | # Only rollback if we've got a previous release 66 | if ($lastRelease.count -eq 2) { 67 | 68 | $previousReleasePath = $lastRelease[1] 69 | 70 | # Remove link to current release 71 | $currentReleasePath = Join-Path $projectPath "Current" 72 | if (Test-Path $currentReleasePath) { 73 | & cmd /c "rmdir ""$currentReleasePath""" 74 | } 75 | 76 | # Link current to previous release 77 | & cmd /c "mklink /J ""$currentReleasePath"" ""$previousReleasePath""" | Out-Null 78 | 79 | # Delete old current release 80 | Remove-Item -Force -Recurse $lastRelease[0] | Out-Null 81 | } 82 | elseif ($lastRelease.count -eq 1) { 83 | 84 | # Return current if no previous release 85 | $previousReleasePath = Join-Path $projectPath "Current" 86 | } 87 | } 88 | 89 | $previousReleasePath 90 | } 91 | 92 | Export-ModuleMember -Function Undo-DeliveryReleasePath 93 | -------------------------------------------------------------------------------- /Modules/PowerDeliveryNode/PowerDeliveryNode.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavonius/powerdelivery/1d284f60b319e6ef6ab60a6a9901bcd3ebb7c889/Modules/PowerDeliveryNode/PowerDeliveryNode.psd1 -------------------------------------------------------------------------------- /Modules/PowerDeliveryNode/PowerDeliveryNode.psm1: -------------------------------------------------------------------------------- 1 | <# PowerDeliveryNode.psm1 2 | 3 | Script for PowerShell module. 4 | 5 | http://www.powerdelivery.io 6 | #> 7 | 8 | # Load cmdlets 9 | $cmdletsDir = (Join-Path $PSScriptRoot "Cmdlets") 10 | gci $cmdletsDir -Filter "*.ps1" | ForEach-Object { . (Join-Path $cmdletsDir $_.Name) } 11 | -------------------------------------------------------------------------------- /Modules/PowerDeliveryNode/Templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eavonius/powerdelivery/1d284f60b319e6ef6ab60a6a9901bcd3ebb7c889/Modules/PowerDeliveryNode/Templates/.gitkeep -------------------------------------------------------------------------------- /PowerDelivery3.template.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | powerdelivery3 5 | 3.0.1 6 | PowerDelivery 3 7 | Jayme Edwards 8 | admin 9 | 10 | http://www.powerdelivery.io/ 11 | http://www.powerdelivery.io/ 12 | https://github.com/eavonius/powerdelivery 13 | https://github.com/eavonius/powerdelivery 14 | https://github.com/eavonius/powerdelivery/issues 15 | http://www.powerdelivery.io/releasenotes.html 16 | false 17 | Inspired by ansible and rails, powerdelivery organizes everything Windows PowerShell can do within a secure, convention-based framework so you can stop being jealous of your linux friends when you deploy to Windows. 18 | Powerdelivery organizes everything Windows PowerShell can do within a secure, convention-based framework so you can stop being jealous of your linux friends when you deploy to Windows. 19 | en-US 20 | http://www.powerdelivery.io/img/masters/choco_logo.png 21 | http://www.opensource.org/licenses/MIT 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{ Templates }} 34 | 35 | 36 | {{ Cmdlets }} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /PowerDelivery3Node.template.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | powerdelivery3node 5 | 3.0.1 6 | PowerDelivery 3 Node 7 | Jayme Edwards 8 | admin 9 | 10 | http://www.powerdelivery.io/ 11 | http://www.powerdelivery.io/ 12 | https://github.com/eavonius/powerdelivery 13 | https://github.com/eavonius/powerdelivery 14 | https://github.com/eavonius/powerdelivery/issues 15 | http://www.powerdelivery.io/releasenotes.html 16 | false 17 | PowerShell cmdlets for use on remote nodes being deployed to with PowerDelivery 3. 18 | PowerShell cmdlets for use on remote nodes being deployed to with PowerDelivery 3. 19 | en-US 20 | http://www.powerdelivery.io/img/masters/choco_logo.png 21 | http://www.opensource.org/licenses/MIT 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {{ Templates }} 33 | 34 | 35 | {{ Cmdlets }} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | powerdelivery 2 | ============= 3 | 4 | Powerdelivery organizes everything Windows Powershell can do within a secure, convention-based framework so you can stop being jealous of your linux friends when you deploy to Windows. 5 | 6 | ### [Full docs on the website](http://powerdelivery.io) 7 | 8 | Discuss powerdelivery in the gitter chat room. Anyone can join! 9 | 10 | Gitter 11 | 12 | ## License 13 | 14 | powerdelivery is released under the [MIT License](http://www.opensource.org/licenses/MIT). 15 | -------------------------------------------------------------------------------- /Release.ps1: -------------------------------------------------------------------------------- 1 | <# Release.ps1 2 | 3 | Increments versions, creates nuget packages, 4 | commits changes, and pushes to chocolatey. 5 | #> 6 | 7 | $ErrorActionPreference = "Stop" 8 | 9 | # Throw if lasterror not zero 10 | # 11 | function SafeInvoke($msg, $cmd) { 12 | Invoke-Command $cmd 13 | if ($lastexitcode -ne 0) { 14 | throw $msg 15 | } 16 | } 17 | 18 | # Updates the PowerShell module version 19 | # 20 | function UpdateModuleVersion($manifestFile, $newVersion) { 21 | 22 | $content = Get-Content $manifestFile 23 | 24 | $newContent = $content -replace "^ModuleVersion = ['|`"].*['|`"]", "ModuleVersion = '$newVersion'" 25 | 26 | Out-File -FilePath $manifestFile -Force -InputObject $newContent 27 | } 28 | 29 | # Inserts file statments into a nuspec for a replacement token 30 | # 31 | function InsertNuspecFiles($content, $token, $path) { 32 | 33 | $files = Get-ChildItem -File "Modules\$path" 34 | 35 | $filesStatement = "" 36 | foreach ($file in $files) { 37 | $filesStatement += [Environment]::NewLine 38 | $filesStatement += "" 39 | } 40 | 41 | $content -replace $token, $filesStatement 42 | } 43 | 44 | # Generates a nuspec file using a template 45 | # 46 | function GenerateNuspec($module, $moduleId, $newVersion) { 47 | 48 | $template = "$moduleId.template.nuspec" 49 | $nuspec = "$moduleId.nuspec" 50 | 51 | $content = Get-Content $template 52 | 53 | $newContent = InsertNuspecFiles -Content $content -Token "{{ Cmdlets }}" -Path "$module\Cmdlets" 54 | $newContent = InsertNuspecFiles -Content $newContent -Token "{{ Templates }}" -Path "$module\Templates" 55 | 56 | Out-File -FilePath $nuspec -Force -InputObject $newContent 57 | 58 | [xml]$nuspecFile = Get-Content $nuspec 59 | 60 | $namespaces = New-Object Xml.XmlNamespaceManager $nuspecFile.NameTable 61 | $namespaces.AddNamespace('nu', 'http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd') 62 | 63 | $versionElement = $nuspecFile.SelectSingleNode('//nu:package/nu:metadata/nu:version', $namespaces) 64 | $versionElement.'#text' = "$newVersion" 65 | 66 | # Update the PowerDeliveryNode module dependency if PowerDelivery 67 | if ($module -eq 'PowerDelivery') { 68 | $nodeDependency = $nuspecFile.SelectSingleNode("//nu:package/nu:metadata/nu:dependencies/nu:dependency[@id='powerdelivery3node']", $namespaces) 69 | $nodeDependency.version = "$newVersion" 70 | } 71 | 72 | $nuspecFullPath = Join-Path (Get-Location) $nuspec 73 | $nuspecFile.Save($nuspecFullPath) 74 | 75 | SafeInvoke -msg "Error running cpack" -cmd { 76 | choco pack "$nuspecFullPath" 77 | } 78 | } 79 | 80 | # Gets the new version for a release based on what's on chocolatey 81 | # 82 | function GetNewModuleVersion($moduleId) { 83 | $packages = $(clist powerdelivery3) -split [Environment]::NewLine 84 | foreach ($package in $packages) { 85 | if ($package.StartsWith("$moduleId ", [System.StringComparison]::InvariantCultureIgnoreCase)) { 86 | $latestVersion = New-Object System.Version -ArgumentList $package.Split(' ')[1] 87 | Write-Host "Latest version on chocolatey is $latestVersion" 88 | return "$($latestVersion.Major).$($latestVersion.Minor).$($latestVersion.Build + 1)" 89 | } 90 | } 91 | 92 | "3.0.0" 93 | } 94 | 95 | # Syncs changes with git 96 | # 97 | function Sync-Git($newVersion) { 98 | 99 | SafeInvoke -msg "Error adding changes to git" -cmd { 100 | git add . 101 | } 102 | 103 | SafeInvoke -msg "Error committing changes to git" -cmd { 104 | git commit --allow-empty -m "Chocolatey $newVersion release." 105 | } 106 | 107 | SafeInvoke -msg "Error pushing changes to git" -cmd { 108 | git push 109 | } 110 | } 111 | 112 | $startDir = Get-Location 113 | 114 | try { 115 | del *.nupkg 116 | 117 | $modules = @{ 118 | PowerDeliveryNode = "PowerDelivery3Node"; 119 | PowerDelivery = "PowerDelivery3" 120 | } 121 | 122 | foreach ($module in $modules.GetEnumerator()) { 123 | $module = $module.Key 124 | $moduleId = $modules[$module] 125 | 126 | $newVersion = GetNewModuleVersion -moduleId $moduleId 127 | 128 | UpdateModuleVersion -manifestFile "Modules\$module\$module.psd1" -newVersion $newVersion 129 | GenerateNuspec -module $module -moduleId $moduleId -newVersion $newVersion 130 | 131 | $nuPkgFile = (gci "$moduleId.*.nupkg").Name 132 | 133 | SafeInvoke -msg "Error publishing to chocolatey" -cmd { 134 | cpush $nuPkgFile 135 | } 136 | 137 | Write-Host "$moduleId successfully released as $newVersion!" -ForegroundColor Green 138 | } 139 | 140 | Sync-Git -newVersion $newVersion 141 | } 142 | finally { 143 | Set-Location $startDir 144 | } -------------------------------------------------------------------------------- /Scripts/Chocolatey/ChocolateyPowerDeliveryUtils.ps1: -------------------------------------------------------------------------------- 1 | <# chocolateyPowerDeliveryUtils.ps1 2 | 3 | Installs and Uninstalls PowerShell modules for PowerDelivery with chocolatey. 4 | #> 5 | 6 | $ErrorActionPreference = 'Stop' 7 | 8 | function Install-PowerDeliveryModule { 9 | [CmdletBinding()] 10 | param( 11 | [Parameter(Position=0, Mandatory=1)][string] $moduleDir, 12 | [Parameter(Position=1, Mandatory=1)][string] $moduleName, 13 | [Parameter(Position=2, Mandatory=1)][string] $packageId 14 | ) 15 | 16 | $moduleDir = "$moduleDir\" 17 | 18 | $psModulePath = [Environment]::GetEnvironmentVariable("PSMODULEPATH", [EnvironmentVariableTarget]::Machine) 19 | 20 | $newEnvVar = $moduleDir 21 | 22 | $caseInsensitive = [StringComparison]::InvariantCultureIgnoreCase 23 | 24 | $pathSegment = "chocolatey\lib\$packageId\" 25 | 26 | if (![String]::IsNullOrWhiteSpace($psModulePath)) { 27 | if ($psModulePath.IndexOf($pathSegment, $caseInsensitive) -lt 0) { # First time installing 28 | if ($psModulePath.EndsWith(";")) { 29 | $psModulePath = $psModulePath.TrimEnd(";") 30 | } 31 | $newEnvVar = "$($psModulePath);$($moduleDir)" 32 | } 33 | else { # Replacing an existing install 34 | $indexOfSegment = $psModulePath.IndexOf($pathSegment, $caseInsensitive) 35 | $startingSemicolon = $psModulePath.LastIndexOf(";", $indexOfSegment, $caseInsensitive) 36 | $trailingSemicolon = $psModulePath.IndexOf(";", $indexOfSegment + $pathSegment.Length, $caseInsensitive) 37 | 38 | if ($startingSemicolon -ne -1) { 39 | $psModulePrefix = $psModulePath.Substring(0, $startingSemicolon) 40 | $newEnvVar = "$($psModulePrefix);$($moduleDir)" 41 | } 42 | if ($trailingSemicolon -ne -1) { 43 | $newEnvVar += $psModulePath.Substring($trailingSemicolon) 44 | } 45 | } 46 | } 47 | else { 48 | $newEnvVar = "%PSMODULEPATH%;$newEnvVar" 49 | } 50 | 51 | [Environment]::SetEnvironmentVariable("PSMODULEPATH", $newEnvVar, [EnvironmentVariableTarget]::Machine) 52 | $Env:PSMODULEPATH = $newEnvVar 53 | 54 | Import-Module $moduleName -Force 55 | } 56 | 57 | function Uninstall-PowerDeliveryModule { 58 | [CmdletBinding()] 59 | param( 60 | [Parameter(Position=0, Mandatory=1)][string] $moduleDir, 61 | [Parameter(Position=1, Mandatory=1)][string] $moduleName, 62 | [Parameter(Position=2, Mandatory=1)][string] $packageId 63 | ) 64 | 65 | try { 66 | Remove-Module $moduleName | Out-Null 67 | } 68 | catch {} 69 | 70 | $moduleDir = "$moduleDir\" 71 | 72 | $psModulePath = [Environment]::GetEnvironmentVariable("PSMODULEPATH", [EnvironmentVariableTarget]::Machine) 73 | 74 | $newEnvVar = $moduleDir 75 | 76 | $caseInsensitive = [StringComparison]::InvariantCultureIgnoreCase 77 | 78 | $pathSegment = "chocolatey\lib\$packageId\" 79 | 80 | if (![String]::IsNullOrWhiteSpace($psModulePath)) { 81 | if ($psModulePath.IndexOf($pathSegment, $caseInsensitive) -ge 0) { 82 | $indexOfSegment = $psModulePath.IndexOf($pathSegment, $caseInsensitive) 83 | $startingSemicolon = $psModulePath.LastIndexOf(";", $indexOfSegment, $caseInsensitive) 84 | $trailingSemicolon = $psModulePath.IndexOf(";", $indexOfSegment + $pathSegment.Length, $caseInsensitive) 85 | 86 | if ($startingSemicolon -ne -1) { 87 | $newEnvVar = $psModulePath.Substring(0, $startingSemicolon) 88 | } 89 | if ($trailingSemicolon -ne -1) { 90 | $newEnvVar += $psModulePath.Substring($trailingSemicolon) 91 | } 92 | 93 | [Environment]::SetEnvironmentVariable("PSMODULEPATH", $newEnvVar, [EnvironmentVariableTarget]::Machine) 94 | $Env:PSMODULEPATH = $newEnvVar 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Scripts/Chocolatey/PowerDelivery/ChocolateyInstall.ps1: -------------------------------------------------------------------------------- 1 | <# ChocolateyInstall.ps1 2 | 3 | Installs PowerDelivery3 with chocolatey. 4 | #> 5 | 6 | $ErrorActionPreference = 'Stop' 7 | 8 | $moduleDir = Split-Path -parent $MyInvocation.MyCommand.Definition 9 | 10 | . (Join-Path $moduleDir 'chocolateyPowerDeliveryUtils.ps1') 11 | 12 | Install-PowerDeliveryModule -moduleDir $moduleDir -moduleName PowerDelivery -packageId powerdelivery3 13 | -------------------------------------------------------------------------------- /Scripts/Chocolatey/PowerDelivery/ChocolateyUninstall.ps1: -------------------------------------------------------------------------------- 1 | <# ChocolateyUninstall.ps1 2 | 3 | Uninstalls PowerDelivery3 with chocolatey. 4 | #> 5 | 6 | $ErrorActionPreference = 'Stop' 7 | 8 | $moduleDir = Split-Path -parent $MyInvocation.MyCommand.Definition 9 | 10 | . (Join-Path $moduleDir 'chocolateyPowerDeliveryUtils.ps1') 11 | 12 | Uninstall-PowerDeliveryModule -moduleDir $moduleDir -moduleName PowerDelivery -packageId powerdelivery3 13 | -------------------------------------------------------------------------------- /Scripts/Chocolatey/PowerDelivery/Init.ps1: -------------------------------------------------------------------------------- 1 | <# Init.ps1 2 | 3 | Initializes PowerDelivery3 when loaded by chocolatey. 4 | #> 5 | 6 | param($installPath, $toolsPath, $package) 7 | 8 | $modulePath = Join-Path $toolsPath PowerDelivery.psm1 9 | Import-Module $modulePath -Force 10 | -------------------------------------------------------------------------------- /Scripts/Chocolatey/PowerDeliveryNode/ChocolateyInstall.ps1: -------------------------------------------------------------------------------- 1 | <# ChocolateyInstall.ps1 2 | 3 | Installs PowerDelivery3Node with chocolatey. 4 | #> 5 | 6 | $ErrorActionPreference = 'Stop' 7 | 8 | $moduleDir = Split-Path -parent $MyInvocation.MyCommand.Definition 9 | 10 | . (Join-Path $moduleDir 'chocolateyPowerDeliveryUtils.ps1') 11 | 12 | Install-PowerDeliveryModule -moduleDir $moduleDir -moduleName PowerDeliveryNode -packageId powerdelivery3node 13 | -------------------------------------------------------------------------------- /Scripts/Chocolatey/PowerDeliveryNode/ChocolateyUninstall.ps1: -------------------------------------------------------------------------------- 1 | <# ChocolateyUninstall.ps1 2 | 3 | Uninstalls PowerDelivery3Node with chocolatey. 4 | #> 5 | 6 | $ErrorActionPreference = 'Stop' 7 | 8 | $moduleDir = Split-Path -parent $MyInvocation.MyCommand.Definition 9 | 10 | . (Join-Path $moduleDir 'chocolateyPowerDeliveryUtils.ps1') 11 | 12 | Uninstall-PowerDeliveryModule -moduleDir $moduleDir -moduleName PowerDeliveryNode -packageId powerdelivery3node 13 | -------------------------------------------------------------------------------- /Scripts/Chocolatey/PowerDeliveryNode/Init.ps1: -------------------------------------------------------------------------------- 1 | <# Init.ps1 2 | 3 | Initializes PowerDelivery3Node when loaded by chocolatey. 4 | #> 5 | 6 | param($installPath, $toolsPath, $package) 7 | 8 | $modulePath = Join-Path $toolsPath PowerDeliveryNode.psm1 9 | Import-Module $modulePath -Force 10 | -------------------------------------------------------------------------------- /Scripts/Nodes/AllowDelivery.ps1: -------------------------------------------------------------------------------- 1 | <# AllowDelivery.ps1 2 | 3 | Allows powerdelivery to deploy to the node that this script is 4 | executed on. Must be run in a PowerShell Administrator console. 5 | 6 | http://www.powerdelivery.io/environments.html#enabling_deployment_to_nodes 7 | #> 8 | param( 9 | [Parameter(Position=0,Mandatory=0)][switch] $AllowAnyPublicAddress 10 | ) 11 | 12 | winrm quickconfig -Force 13 | Enable-WSManCredSSP -Role Server -Force | Out-Null 14 | Enable-PSRemoting -Force -SkipNetworkProfileCheck 15 | 16 | if ($AllowPublicAnyAddress) { 17 | 18 | # This will allow PowerShell to connect from non-domain computers if necessary. 19 | # Without it, this computer will only allow connections when the user running 20 | # powerdelivery is on the same subnet. 21 | Set-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" -RemoteAddress Any 22 | } 23 | 24 | Write-Host "Powerdelivery has been permitted to deploy to this computer." 25 | -------------------------------------------------------------------------------- /Test.ps1: -------------------------------------------------------------------------------- 1 | <# Test.ps1 2 | 3 | Tests powerdelivery with Pester https://github.com/pester/Pester. 4 | You must install Pester first. With chocolatey: cinst pester 5 | #> 6 | $startDir = Get-Location 7 | 8 | Set-Location .\Tests 9 | 10 | try { 11 | Invoke-Pester 12 | } 13 | finally { 14 | Set-Location $startDir 15 | } 16 | -------------------------------------------------------------------------------- /Tests/GetKeyBytes.Tests.ps1: -------------------------------------------------------------------------------- 1 | Import-Module ..\Modules\PowerDelivery\PowerDelivery.psd1 -Force 2 | 3 | Describe "GetKeyBytes" { 4 | InModuleScope PowerDelivery { 5 | $myDocumentsFolder = "TestDrive:\MyDocuments" 6 | $testKey = "W6H8qMR9Q5rn3g6gbc9fy0EpyTop80BRSq5v5zDSK4s=" 7 | $testKeyDir = "TestDrive:\MyDocuments\PowerDelivery\Keys\MyAppDelivery" 8 | $testKeyPath = "$testKeyDir\MyKey.key" 9 | 10 | It "should verify existence of key" { 11 | Mock GetMyDocumentsFolder { $myDocumentsFolder } 12 | { GetKeyBytes -ProjectDir "MyAppDelivery" -KeyName "MyKey" -ThrowOnError } | 13 | Should Throw "Key not found at $testKeyPath" 14 | } 15 | 16 | It "should return null on no key without -ThrowOnError" { 17 | Mock GetMyDocumentsFolder { $myDocumentsFolder } 18 | $keyBytes = GetKeyBytes -ProjectDir "MyAppDelivery" -KeyName "MyKey" 19 | $keyBytes | Should Be $null 20 | } 21 | 22 | It "should get valid key" { 23 | New-Item -ItemType Directory $testKeyDir 24 | Set-Content -Path $testKeyPath -Value $testKey 25 | Mock GetMyDocumentsFolder { $myDocumentsFolder } 26 | $keyBytes = GetKeyBytes -ProjectDir "MyAppDelivery" -KeyName "MyKey" 27 | $keyBytes | Should Be @(91, 161, 252, 168, 196, 125, 67, 154, 231, 222, 28 | 14, 160, 109, 207, 95, 203, 65, 41, 201, 58, 41, 29 | 243, 64, 81, 74, 174, 111, 231, 48, 210, 43, 139) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/GetProjectDirectory.Tests.ps1: -------------------------------------------------------------------------------- 1 | Import-Module ..\Modules\PowerDelivery\PowerDelivery.psd1 -Force 2 | 3 | Describe "GetProjectDirectory" { 4 | InModuleScope PowerDelivery { 5 | 6 | It "should validate required directories" { 7 | { GetProjectDirectory } | 8 | Should Throw "This command must be run from within a powerdelivery project directory." 9 | } 10 | 11 | It "should return path if valid" { 12 | Mock Test-Path { return $true } 13 | Mock Get-Location { return "TestDrive:\MyApp\MyAppDelivery" } 14 | $projectDir = GetProjectDirectory 15 | $projectDir | Should Be 'TestDrive:\MyApp\MyAppDelivery' 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/New-DeliveryKey.Tests.ps1: -------------------------------------------------------------------------------- 1 | Import-Module ..\Modules\PowerDelivery\PowerDelivery.psd1 -Force 2 | 3 | Describe "New-DeliveryKey" { 4 | InModuleScope PowerDelivery { 5 | 6 | It "should not overwrite existing" { 7 | Mock GetProjectDirectory { "TestDrive:\MyAppDelivery" } 8 | Mock GetMyDocumentsFolder { "TestDrive:\MyDocuments" } 9 | Mock Test-Path { $true } 10 | 11 | { New-DeliveryKey "TestKey" } | 12 | Should Throw "Key TestDrive:\MyDocuments\PowerDelivery\Keys\MyAppDelivery\TestKey.key already exists." 13 | } 14 | 15 | It "should create key" { 16 | Mock GetProjectDirectory { "TestDrive:\MyAppDelivery" } 17 | Mock GetMyDocumentsFolder { "TestDrive:\MyDocuments" } 18 | Mock Test-Path { $false } 19 | 20 | Mock Write-Host {} -Verifiable -ParameterFilter { $Object -eq "Key written to ""TestDrive:\MyDocuments\PowerDelivery\Keys\MyAppDelivery\TestKey.key""" } 21 | 22 | New-DeliveryKey "TestKey" 23 | 24 | Assert-VerifiableMocks 25 | 26 | "TestDrive:\MyDocuments\PowerDelivery\Keys\MyAppDelivery\TestKey.key" | Should Exist 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/New-DeliveryRole.Tests.ps1: -------------------------------------------------------------------------------- 1 | Import-Module ..\Modules\PowerDelivery\PowerDelivery.psd1 -Force 2 | 3 | Describe "New-DeliveryRole" { 4 | InModuleScope PowerDelivery { 5 | 6 | It "should not overwrite existing" { 7 | Mock GetProjectDirectory { "TestDrive:\MyAppDelivery" } 8 | Mock Test-Path { $true } 9 | 10 | { New-DeliveryRole "Chocolatey" } | 11 | Should Throw "Directory .\Roles\Chocolatey already exists." 12 | } 13 | 14 | It "should create role" { 15 | Mock GetProjectDirectory { "TestDrive:\MyAppDelivery" } 16 | Mock Test-Path { $false } 17 | Mock Write-Host {} -Verifiable -ParameterFilter { $Object -eq "Role created at "".\Roles\Chocolatey""" } 18 | 19 | New-DeliveryRole "Chocolatey" 20 | 21 | Assert-VerifiableMocks 22 | 23 | "TestDrive:\MyAppDelivery\Roles\Chocolatey\Always.ps1" | Should Contain "Delivery:Role -Up {" 24 | "TestDrive:\MyAppDelivery\Roles\Chocolatey\Migrations" | Should Exist 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/ValidateNewFileName.Tests.ps1: -------------------------------------------------------------------------------- 1 | Import-Module ..\Modules\PowerDelivery\PowerDelivery.psd1 -Force 2 | 3 | Describe "ValidateNewFileName" { 4 | InModuleScope PowerDelivery { 5 | 6 | It "should not allow spaces" { 7 | { ValidateNewFileName -Type "test type" -FileName "hey you" } | 8 | Should Throw "Please use a test type that only includes alphanumeric characters and no spaces." 9 | } 10 | 11 | It "should not allow special characters" { 12 | { ValidateNewFileName -Type "test type" -FileName "hey#you" } | 13 | Should Throw "Please use a test type that only includes alphanumeric characters and no spaces." 14 | } 15 | 16 | It "should be valid" { 17 | { ValidateNewFileName -Type "test type" -FileName "heyyou" } | Should Not Throw 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Verify.ps1: -------------------------------------------------------------------------------- 1 | <# Verify.ps1 2 | 3 | Verifies powerdelivery with chocolatey test environment https://github.com/chocolatey/chocolatey-test-environment. 4 | You must pull the source into C:\Projects\chocolatey-test-environment and follow the instructions for installing 5 | virtualbox 4.8x, vagrant, sahara, and doing a "vagrant up" and "vagrant sandbox on" successfully before running this. 6 | 7 | Also edit the Vagrantfile included with chocolatey-test-environment and update # THIS IS WHAT YOU CHANGE line 2 to: 8 | 9 | choco.exe install -fdvy powerdelivery3 --allow-downgrade --source "'c:\\packages;http://chocolatey.org/api/v2/'" 10 | #> 11 | $startDir = Get-Location 12 | $verifierDir = "C:\Projects\chocolatey-test-environment" 13 | $packagesDir = "C:\Projects\chocolatey-test-environment\packages" 14 | 15 | cp powerdelivery3.*.nupkg $packagesDir -force 16 | cp powerdelivery3node.*.nupkg $packagesDir -force 17 | 18 | Set-Location $verifierDir 19 | 20 | try { 21 | vagrant provision 22 | } 23 | finally { 24 | Set-Location $startDir 25 | } 26 | --------------------------------------------------------------------------------