Rename or deploy additional files to Azure Web App - azure-devops

As part of my release pipeline in Azure DevOps, I wish to rename some .config files to .config.disabled, after my "Deploy Azure App Service" task has finished.
I have tried to add an additional "Deploy Azure App Service" task, but that seems to overwrite the previous one, leaving only the .config.disabled files in the wwwroot.
Is there any other way (other than FTP upload task), which can be used to rename/deploy a subset of files, in an Azure web app?

If you don't want to use FTP, you might have some success trying to use the Kudu service that backs the Azure Web App.
To view the Kudu service, navigate to https://<your-web-app-name>.scm.azurewebsites.net
After you've authenticated, you'll discover there's a way to browse files, view running processes and an interactive console that allows you to run basic DOS or PowerShell commands against the file system.
The interactive console is great, but to automate it you can issue commands against the file system using a REST API.
There are several examples of issuing commands to the server. In PowerShell:
# authenticate with Azure
Login-AzureRmAccount
$resoureGroupName = "your-web-app-name"
$websiteName = "your-resource-group-name"
$env = #{
command= 'Set COMPUTERNAME'
dir= 'site'
}
$json = $env | ConvertTo-Json
$env2 = #{
command= 'copy filename.config file.config.notused'
dir= 'site\wwwroot'
}
$json2 = $env2 | ConvertTo-Json
$env3 = #{
command= 'delete filename.config'
dir= 'site\wwwroot'
}
$json3 = $env3 | ConvertTo-Json
# setup auth header
$website = Get-AzureWebsite -Name $websiteName
$username = $website.PublishingUsername
$password = $website.PublishingPassword
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
$apiBaseUrl = "https://$($website.Name).scm.azurewebsites.net/api"
[System.Uri]$Uri = $apiBaseUrl
# get all the vms in the web-app
$instances = Get-AzureRmResource -ResourceGroupName $resoureGroupName `
-ResourceType Microsoft.Web/sites/instances `
-ResourceName $websiteName `
-ApiVersion 2018-02-01
#loop through instances
foreach($instance in $instances)
{
$instanceName = $instance.Name
Write-Host "`tVM Instance ID `t`t: " $instanceName
#Now execute 'SET COMPUTER' cmd
$cookie= New-Object System.Net.Cookie
$cookie.Name = "ARRAffinity"
$cookie.Value = $instanceName
$Cookie.Domain = $uri.DnsSafeHost
$session=New-Object Microsoft.Powershell.Commands.WebRequestSession
$session.Cookies.add($cookie)
$response = Invoke-RestMethod -Uri "$apiBaseUrl/command" `
-Headers #{Authorization=("Basic {0}" `
-f $base64AuthInfo)} `
-Method Post -Body $json `
-ContentType 'application/json' `
-WebSession $session
Write-Host "`tVM Instance Name `t: " $response
# perform copy file
$response = Invoke-RestMethod -Uri "$apiBaseUrl/command" `
-Headers #{Authorization=("Basic {0}" `
-f $base64AuthInfo)} `
-Method Post -Body $json2 `
-ContentType 'application/json' `
-WebSession $session
Write-Host "`tCopy file result `t: " $response
# perform delete file
$response = Invoke-RestMethod -Uri "$apiBaseUrl/command" `
-Headers #{Authorization=("Basic {0}" `
-f $base64AuthInfo)} `
-Method Post -Body $json3 `
-ContentType 'application/json' `
-WebSession $session
Write-Host "`tCopy file result `t: " $response
}
The file system supports basic commands, like copy and delete so you could easily rename the file.

Related

Authenticate to Azure DevOps without user's DevOps PAT

Currently we use an approach to reach DevOps and trigger "release pipelines" from a specific VM1 by utilizing user's DevOps PAT. We run PowerShell commands below at VM1:
$userPatToken = "xxxdfdgklfdgofkglfg4565gfhgfhgfh4gf54h54545fhfghfdffg"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f "", $userPatToken)))
$url = "https://vsrm.dev.azure.com/MyOrg/MyProject/_apis/release/releases?definitionId=7&$top=100&api-version=6.0"
Invoke-RestMethod -Method Get -Uri $url -ContentType "application/json" -Headers #{Authorization = ("Basic {0}" -f $base64AuthInfo) }
The user is AAD one, is there a way to use it's let say AAD credentials and authenticate to DevOps and do the same?
Or may there is a way to use VMs system managed (or any user managed) identity to authenticate into DevOps and trigger release pipelines? We do not want to be dependent of the user's PAT.
It should be written in PowerShell.
If you don't want to use the PAT, you can install Az powershell module, login with a user account which has the permission in your devops org via Connect-AzAccount.
Then, you can get the token via below command. Please note don't change the 499b84ac-1321-427f-aa17-267ca6975798 in the script, it is the well-known resource id of the DevOps REST API.
$token = (Get-AzAccessToken -ResourceUrl "499b84ac-1321-427f-aa17-267ca6975798").Token
Then, you can pass the token to your powershell script. You can find more details/sample script here.
Edit:
Add username&Password automation script sample:
Install-Module -Name Az -Confirm:$False -Force -AllowClobber
Import-Module Az
$username = "useremail"
$password = "password"
$SecurePassword = ConvertTo-SecureString "$password" -AsPlainText -Force
$credentials = New-Object System.Management.Automation.PSCredential($username, $SecurePassword)
Connect-AzAccount -Credential $credentials -TenantId yourTenantID
$token = (Get-AzAccessToken -ResourceUrl "499b84ac-1321-427f-aa17-267ca6975798").Token
$URL = 'https://dev.azure.com/{orgname}/{Projectname}/_apis/pipelines/{pipelineID}/runs?api-version=6.0-preview.1'
$header = #{
'Authorization' = 'Bearer ' + $token
'Content-Type' = 'application/json'
}
$body = #"
{
"resources": {
"repositories": {
"self": {
"refName": "refs/heads/master"
}
}
}
}
"#
Invoke-RestMethod -Method Post -Uri $URL -Headers $header -Body $body

Save-AzrWebApp func downloads a wrong SourcePath

I'm using Save-AzrWebApp function to downloads files from an Azure Web App.
How to do it is described here: https://blog.ipswitch.com/how-to-copy-files-from-an-azure-app-service-with-powershell
My problem is: it doesn't matter which SourcePath I set, it always downloads me files from wwwroot folder.
Code example that I use:
$syncParams = #{
SourcePath = '\wwwroot\history'
TargetPath = $TargetPath
ComputerName = "https://$Name.scm.azurewebsites.net:443/msdeploy.axd?site=$Name"
Credential = $Credential
}
Sync-Website #syncParams
Get-Item -Path $TargetPath
Actually it doesn't matter what I put into SourcePath (even not existing path) it will download content of wwwroot.
How to use it in a proper way?
If you want to download file from web app, you could use this web app kudu api via powershell.
Try the command below, it works fine on my side.
$creds = Invoke-AzureRmResourceAction -ResourceGroupName joywebapp -ResourceType Microsoft.Web/sites/config -ResourceName joywebapp2/publishingcredentials -Action list -ApiVersion 2015-08-01 -Force
$username = $creds.Properties.PublishingUserName
$password = $creds.Properties.PublishingPassword
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username, $password)))
$apiUrl = "https://joywebapp2.scm.azurewebsites.net/api/vfs/site/wwwroot/Content/Site.css"
Invoke-RestMethod -Uri $apiUrl -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo);"If-Match"="*"} -Method GET -ContentType "multipart/form-data" -OutFile "C:\Users\joyw\Desktop\test.css"
Test result:
Update:
If you want to download a folder, you can use the Zip api in the doc I mentioned, it allows downloading folder as a zip file.
Sample command:
$creds = Invoke-AzureRmResourceAction -ResourceGroupName joywebapp -ResourceType Microsoft.Web/sites/config -ResourceName joywebapp2/publishingcredentials -Action list -ApiVersion 2015-08-01 -Force
$username = $creds.Properties.PublishingUserName
$password = $creds.Properties.PublishingPassword
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username, $password)))
$apiUrl = "https://joywebapp2.scm.azurewebsites.net/api/zip/site/wwwroot/Scripts/"
Invoke-RestMethod -Uri $apiUrl -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo);"If-Match"="*"} -Method GET -ContentType "multipart/form-data" -OutFile "C:\Users\joyw\Desktop\Scripts.zip"
Note:The zip doesn't include the top folder itself. Make sure you include
the trailing slash, e.g, I download the Scripts folder, we need to use Scripts/ in the apiUrl.

How to access Kudu in Azure using power shell script

I am trying to access Kudu through power shell script. Link looks like: https://adc-dev.scm.azurewebsites.net. I need to copy war file located in D:\home\site\wwwroot\bin\apache-tomcat-8.0.33\webapps location in above link.
Currently I am deploying the war file using VSTS by adding FTP task. But before deploying the latest war I would like to take backup of the old war in some location in Azure Kudu location say like: D:\home\site\wwwroot\bin\apache-tomcat-8.0.33 (root folder to the war location). So after that I can remove the war and deploy the latest war file in Kudu.
How to do this? I mean how to access kudu using power shell script. Please suggest me.
To access Kudu API, get your WebApp:
$app = Get-AzWebApp -ResourceGroupName "your RG" -Name "your App"
Then get publishing credentials for the app:
$resourceName = "$($app.Name)/publishingcredentials";
$resourceType = "Microsoft.Web/sites/config";
$publishingCredentials = Invoke-AzResourceAction `
-ResourceGroupName $app.ResourceGroup `
-ResourceType $resourceType `
-ResourceName $resourceName `
-Action list `
-ApiVersion $apiVersion `
-Force;
Format the username/password suitable for a HTTP-request:
$user = $publishingCredentials.Properties.PublishingUserName;
$pass = $publishingCredentials.Properties.PublishingPassword;
$creds = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("${user}:${pass}")));
Finally, you can access the Kudu API:
$header = #{
Authorization = "Basic $creds"
};
$kuduApiBaseUrl = "https://$($app.Name).scm.azurewebsites.net";
Example, check if extension is installed:
$extensionName = "Microsoft.AspNetCore.AzureAppServices.SiteExtension";
$kuduApiUrl = "$kuduApiBaseUrl/api/siteextensions/$extensionName";
$response = Invoke-RestMethod -Method 'Get' -Uri $kuduApiUrl -Headers $header;
Example, get list of available extensions:
$kuduApiUrl = "$kuduApiBaseUrl/api/extensionfeed";
$response = Invoke-RestMethod -Method 'Get' -Uri $kuduApiUrl -Headers $header;
Example, install an extension:
$kuduApiUrl = "$kuduApiBaseUrl/api/siteextensions";
$response = Invoke-RestMethod -Method 'Put' -Uri $kuduApiUrl -Headers $header;
API-details are at https://github.com/projectkudu/kudu/wiki/REST-API
Also deployment slots can be accessed. App config needs to be retrieved for the slot and a minor modification of the base URL is needed.
You can refer to this thread below to know how to call Kudu API through Azure PowerShell in VSTS build/release:
Remove files and foldes on Azure before a new deploy from VSTS
Regarding copy file through Kudu, you can use Command Kudu API (Post /api/command):
Kudu REST API
Update:
Simple sample to call Command through Kudu API:
function RunCommand($dir,$command,$resourceGroupName, $webAppName, $slotName = $null){
$kuduApiAuthorisationToken = Get-KuduApiAuthorisationHeaderValue $resourceGroupName $webAppName $slotName
$kuduApiUrl="https://$webAppName.scm.azurewebsites.net/api/command"
$Body =
#{
"command"=$command;
"dir"=$dir
}
$bodyContent=#($Body) | ConvertTo-Json
Write-Host $bodyContent
Invoke-RestMethod -Uri $kuduApiUrl `
-Headers #{"Authorization"=$kuduApiAuthorisationToken;"If-Match"="*"} `
-Method POST -ContentType "application/json" -Body $bodyContent
}
RunCommand "site\wwwroot\bin\apache-tomcat-8.0.33\webapps" "copy xx.war ..\xx.war /y" "[resource group]" "[web app]"
You can use below code to access kudu apis from powershell -
//function to Get webapp's publishing credentials
function Get-AzWebAppPublishingCredentials($resourceGroupName, $webAppName, $slotName = $null) {
if ([string]::IsNullOrWhiteSpace($slotName) -or $slotName.ToLower() -eq "production") {
$resourceType = "Microsoft.Web/sites/config"
$resourceName = "$webAppName/publishingcredentials"
}
else {
$resourceType = "Microsoft.Web/sites/slots/config"
$resourceName = "$webAppName/$slotName/publishingcredentials"
}
$publishingCredentials = Invoke-AzResourceAction -ResourceGroupName $resourceGroupName -ResourceType $resourceType -ResourceName $resourceName -Action list -ApiVersion 2015-08-01 -Force
return $publishingCredentials
}
//function to get authorization header from publishing credentials
function Get-KuduApiAuthorisationHeaderValue($resourceGroupName, $webAppName, $slotName = $null) {
$publishingCredentials = Get-AzWebAppPublishingCredentials $resourceGroupName $webAppName $slotName
$ret = #{ }
$ret.header = ("Basic {0}" -f [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $publishingCredentials.Properties.PublishingUserName, $publishingCredentials.Properties.PublishingPassword))))
$ret.url = $publishingCredentials.Properties.scmUri
return $ret
}
//function to call kudu api e.g. to get a file from webapp
function Get-FileFromWebApp($resourceGroupName, $webAppName, $slotName = "", $kuduPath) {
$KuduAuth = Get-KuduApiAuthorisationHeaderValue $resourceGroupName $webAppName $slotName
$kuduApiAuthorisationToken = $KuduAuth.header
$kuduApiUrl = $KuduAuth.url + "/api/vfs/$kuduPath"
Write-Host " Downloading File from WebApp. Source: '$kuduApiUrl'." -ForegroundColor DarkGray
$tmpPath = "$($env:TEMP)\$([guid]::NewGuid()).json"
$null = Invoke-RestMethod -Uri $kuduApiUrl `
-Headers #{"Authorization" = $kuduApiAuthorisationToken; "If-Match" = "*" } `
-Method GET `
-ContentType "application/json" `
-OutFile $tmpPath
$ret = (Get-Content $tmpPath) | Out-String | ConvertFrom-Json
Remove-Item $tmpPath -Force
return $ret
}

Azure Web App - upload package from powershell

I've used New-AzureRmWebApp to create WebApp from powershell. But I don't find any cmdlet to upload package to created Azure Web App :( Looking for AzureRm cmdlet in specific.
If you are co-admin of your subscription,you can use command Publish-AzureWebsiteProject -Name site1 -Package .\WebApplication1.zip to do that.We also can use kudu Rest API (Zip).More detail about how to get authorization please refer to another SO thread
kudu Rest Api sample code:
$website = Get-AzureWebsite -Name "WebAppSampleFtn2"
$username = $website.PublishingUsername
$password = $website.PublishingPassword
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
$userAgent = "powershell/1.0"
$apiBaseUrl = "https://$($website.Name).scm.azurewebsites.net/api/zip/site/wwwroot"
$filePath = "D:\package\PackageTmp.zip"
Invoke-RestMethod -Uri $apiBaseUrl -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -UserAgent $userAgent -Method PUT -InFile $filePath -ContentType "multipart/form-data"

Azure Websites Kudu REST API - Authentication

I'm trying to use PowerShell to put an updated content file onto an Azure Website via the REST API. However, when supplying my credentials into Invoke-RestMethod -Credentials I am returned the HTML of the standard Azure login page.
How can I authenticate with Kudu from PowerShell? Thanks.
You can first get the website via Powershell and then use the publish credentials from the website to call the Kudu REST API. The example below will get the Kudu version.
$website = Get-AzureWebsite -Name "WebsiteName"
$username = $website.PublishingUsername
$password = $website.PublishingPassword
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
$apiBaseUrl = "https://$($website.Name).scm.azurewebsites.net/api"
$kuduVersion = Invoke-RestMethod -Uri "$apiBaseUrl/environment" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET
In the new ARM world and with the latest PowerShell, you'll need to make some adjustments to #Seth's answer.
Specifically, the way you obtain the publishing creds is different, which is the first 3 lines. The rest I shamelessly copied from #Seth to complete the snippet.
Make sure to replace YourResourceGroup/YourWebApp as appropriate:
$creds = Invoke-AzureRmResourceAction -ResourceGroupName YourResourceGroup -ResourceType Microsoft.Web/sites/config -ResourceName YourWebApp/publishingcredentials -Action list -ApiVersion 2015-08-01 -Force
$username = $creds.Properties.PublishingUserName
$password = $creds.Properties.PublishingPassword
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
$apiBaseUrl = "https://$($website.Name).scm.azurewebsites.net/api"
$kuduVersion = Invoke-RestMethod -Uri "$apiBaseUrl/environment" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET