Out-AzureService.ps1
function Out-AzureService { <# .Synopsis Creates a an Azure Service Deployment pack, definition, and configuration file .Description Uses the Azure SDK tool CSPack to create a deployment package (cspkg) and associated deployment files. .Link New-AzureServiceDefinition .Link Publish-AzureService #> [OutputType([IO.FileInfo])] param( # The Service DefinitionXML [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [ValidateScript({ $isServiceDefinition = $_.NameTable.Get("http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition") if (-not $IsServiceDefinition) { throw "Input must be a ServiceDefinition XML" } return $true })] [Xml] $ServiceDefinition, # The output directory for the azure service. [Parameter(Mandatory=$true)] [string] $OutputPath, # If set, will look for a specific Azure SDK Version [Version] $SdkVersion, # The number of instances to create [Uint32] $InstanceCount = 2, # The operating system family [ValidateSet("2K8R2","2012")] [string] $Os = "2012" ) begin { #region Find CSPack $programFiles = if ($env:ProgramW6432) { $env:ProgramW6432 } else { $env:ProgramFiles } $azureSdkDir = Get-ChildItem "$programFiles\Windows Azure SDK", "$programFiles\Microsoft SDKs\Windows Azure\.NET SDK" -Force -ErrorAction SilentlyContinue if ($azureSdkDir) { $latestcsPack = $azureSdkDir | Sort-Object { $_.Name.Replace('v', '') -as [Version] } | Where-Object { if ($sdkVersion) { $_.Name.Replace('v', '') -eq $SdkVersion } else { return $true } } | Select-Object -Last 1 | Get-ChildItem -Filter 'bin' | Get-ChildItem -Filter 'cspack.exe' if ($latestCsPack) { $csPack = Get-Command $latestCsPack.fullname } } else { $latestCSPack = $csPack = Get-Command $psScriptRoot\Tools\cspack.exe } #endregion Find CSPAck } process { if (-not $latestCSPack) { Write-Error "Azure SDK tool CSPack not found" return } $osFamily = if ($os -eq '2K8R2') { 2 } elseif ($os -eq '2012') { 3 } $temporaryServiceDirectory = New-Item -ItemType Directory -Path "$env:Temp\$(Get-Random).azureService" $serviceName = $ServiceDefinition.ServiceDefinition.name try { $null = $ServiceDefinition.CreateXmlDeclaration("1.0", "utf8", $null) } catch {} $serviceDefinitionFile = Join-Path $temporaryServiceDirectory "$serviceName.csdef" $ServiceDefinition.Save($serviceDefinitionFile) $workingDirectory = Split-Path $serviceDefinitionFile $leaf = Split-Path $serviceDefinitionFile -Leaf $configurationFile = "$serviceName.cscfg" $arguments = @("$leaf") $roles = @($ServiceDefinition.ServiceDefinition.WebRole), @($ServiceDefinition.ServiceDefinition.WorkerRole) + @($ServiceDefinition.ServiceDefinition.VirtualMachineRole) $xmlNamespace = @{'ServiceDefinition'='http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition'} $selectXmlParams = @{ XPath = '//ServiceDefinition:WebRole|//ServiceDefinition:WorkerRole|//ServiceDefinition:VirtualMachineRole' Namespace = $xmlNamespace } $roles = @(Select-Xml -Xml $ServiceDefinition @selectXmlParams | Select-Object -ExpandProperty Node) #$roles[0] $startupBin = "$temporaryServiceDirectory\Startup\bin" New-Item $startupBin -ErrorAction SilentlyContinue -Force -ItemType Directory | Out-Null #region Create roles $firstSitePhysicalDirectory = $null foreach ($role in $roles) { $roleDir = Join-Path $temporaryServiceDirectory $role.Name $null = New-Item -ItemType Directory -Path $roleDir $roleBinDir = Join-Path $temporaryServiceDirectory "$($role.Name)_bin" $null = New-Item -ItemType Directory -Path $roleBinDir $roleBin = Join-Path $roleBinDir "bin" $null = New-Item -ItemType Directory -Path $roleBin # The azure sdk requires a binary, so give them a binary Add-Type -OutputAssembly "$roleBin\Placeholder.dll" -TypeDefinition @" namespace Namespace$(Get-Random) { public class Stuff { public int StuffCount; } } "@ $configSettingsChunk = "<ConfigurationSettings />" $arguments+= "/role:$($role.Name);$($role.Name)_bin" if ($role.ConfigurationSettings) { $configSettingsChunk = "<ConfigurationSettings>" foreach ($configSetting in $role.ConfigurationSettings.Setting) { $configSettingsChunk += $configSetting.innerXml $null = $configSetting.RemoveAttribute('value') } $configSettingsChunk += "</ConfigurationSettings>" $ServiceDefinition.Save($serviceDefinitionFile) } if ($role.Startup) { $c = 0 foreach ($task in $role.Startup.Task) { $c++ # Translate ScriptBlock start up tasks, which, alas, don't directly exist in Azure, into .cmd startup tasks if ($task.ScriptBlock) { $null = $task.SetAttribute('commandLine', "startupScript${c}.cmd") # Create the cmd file $scriptFile = "`$scriptBlockParameters = @{} `$serviceName = '$($serviceDefinition.Name)' " # If parameters were supplied, put them into a hashtable if ($task.Parameters) { foreach ($parameter in $task.Parameters) { $scriptFile += " `$scriptBlockParameters.'$($Parameter.Name)' = '$($parameter.Value)' " } } # Run the PowerShell script and exit $scriptFile += " `$exitCode = 0 & { $($task.ScriptBlock.'#text') } @scriptBlockParameters exit `$exitCode " # Convert script into a base64 encoded file, then run it $b64 = [Convert]::ToBase64String([Text.Encoding]::unicode.GetBytes($scriptFile)) $cmdFile = "powershell.exe -executionpolicy bypass -encodedCommand $b64" $cmdFile | Set-Content "$roleBin\startupScript${c}.cmd" #$scriptFile > "$roleBin\startupScript${c}.ps1" } # Batch scripts are easier, just add a .cmd file and away we go. if ($task.Batch) { $null = $task.SetAttribute('commandLine', "startupScript${c}.cmd") $cmdFile = $task.Batch $cmdFile | Set-Content "$roleBin\startupScript${c}.cmd" } foreach ($i in @($task.GetEnumerator())) { $null = try { $task.RemoveChild($i) } catch { } } } $ServiceDefinition.Save($serviceDefinitionFile) } $roleConfigChunk += "<Role name='$($role.Name)'> $configSettingsChunk <Instances count='$InstanceCount' /> </Role>" $sites = $roles = @(Select-Xml -Xml $ServiceDefinition -Namespace $xmlNamespace -XPath //ServiceDefinition:Site | Select-Object -ExpandProperty Node) if ($sites) { foreach ($site in $sites ) { if (-not $firstSitePhysicalDirectory) { $firstSitePhysicalDirectory= $site.PhysicalDirectory} $webConfigFile = Join-Path $site.PhysicalDirectory "Web.Config" if (-not (Test-Path $webConfigFile)) { ' <configuration> <system.web> <customErrors mode="Off"/> </system.web> </configuration> ' | Set-Content -path $webConfigFile } } } } #endregion Create roles $cscfgXml = [xml]@" <ServiceConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" serviceName="$serviceName" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily='$osFamily' osVersion='*'> $RoleConfigChunk </ServiceConfiguration> "@ #region Push to the output directory, then run CSPack Push-Location $workingDirectory $results = & $csPack $arguments Pop-Location #endregion Push to the output directory, then run CSPack $errs =$results -like "*Error*:*" if ($errs) { foreach ($err in $errs) { Write-Error $err.Substring($err.IndexOf(":") + 1) } return } $csdef = $serviceDefinitionFile $cspkg = Join-Path $workingDirectory "$serviceName.cspkg" if (-not $outputPath) { $serviceDeploymentRoot = "$psScriptRoot\AzureServices" if (-not (Test-Path $serviceDeploymentRoot)) { $null = New-Item -ItemType Directory -Path $serviceDeploymentRoot } $serviceDropDirectory = "$serviceDeploymentRoot\$serviceName" if (-not (Test-Path $serviceDropDirectory)) { $null = New-Item -ItemType Directory -Path $serviceDropDirectory } $nowString = (Get-Date | Out-String).Trim().Replace(":", "-") $thisDropDirectory =Join-Path $serviceDropDirectory $nowString if (-not (Test-Path $thisDropDirectory)) { $null = New-Item -ItemType Directory -Path $thisDropDirectory } } else { $unResolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($outputPath) if (-not (Test-Path $unResolvedPath)) { $newPath = New-Item -ItemType Directory $unResolvedPath if ($newPath) { $thisDropDirectory = "$newPath" } } else { $thisDropDirectory = "$unResolvedPath" } } #Move-Item -LiteralPath $cscfg -Destination "$thisDropDirectory" $cscfg = Join-Path $thisDropDirectory $configurationFile if (Test-Path $cscfg) { Remove-Item -Force $cscfg } $cscfgXml.Save("$cscfg") Move-Item -LiteralPath $csdef -Destination "$thisDropDirectory" -Force Move-Item -LiteralPath $cspkg -Destination "$thisDropDirectory" -Force Remove-Item -Recurse -Force $workingDirectory Get-ChildItem $thisDropDirectory -Force } } |