Azure Devops Deployment: Useful Powershell Scripts

Sergey Baranov on September 13, 2020

Azure DevOps is a popular platform for software development and deployment. The platform provides a big list of build-in tasks for build and deployment, which is quite useful. Nevertheless, they are often not enough and you have to implement your own scripts. I prefer doing this by adding PowerShell scripts to the pipelines and here I would like to share my top PowerShell scripts that can be helpful for every developer.

Disclaimer: avoid using IIS Web App Manage tasks for IIS and App Pool managing.

Problem: it fails with an error if the current status is not the same as expected.

Example: the Stop App Pool task will fail if an application pool is already stopped.

  1. Stopping a website if it exists or creating a new one and stopping it if it doesn’t exist

    
    Import-Module WebAdministration
    $siteName = 'website.com';
    $path = "c:\inetpub\wwwroot\website.com"
    
    $exist = (Get-Website -name $siteName) -ne $null
    
    if($exist){
    	$status = Get-WebsiteState -name $siteName
    
    	if ($status.Value -ne 'Stopped'){
    	   Write-Output ('Stopping IIS Website: {0}' -f $siteName)
    		Stop-WebSite -Name $siteName
    	}
    }
    else{
            # create new IIS website and 80 port binding
    	New-Item IIS:\Sites\$siteName -bindings @{protocol="http";bindingInformation=":80:" + $siteName} -physicalPath $path
    	$status = Get-WebsiteState -name $siteName
    	if ($status.Value -ne 'Stopped'){
    	   Write-Output ('Stopping IIS Website: {0}' -f $siteName)
    		Stop-WebSite -Name $siteName
    	}
    }
    

  2. Stopping an App Pool if it exists or creating a new one and stopping it if it doesn’t exist

    
    Import-Module WebAdministration
    $siteName = 'website.com';
    
    if(Test-Path IIS:\AppPools\$siteName){
    	$status = Get-WebAppPoolState -name $siteName
    
    	if ($status.Value -ne 'Stopped'){
    	   Write-Output ('Stopping Application Pool: {0}' -f $siteName)
    		Stop-WebAppPool -Name $siteName
    	}
    }
    else{
    	New-Item IIS:\AppPools\$siteName
    	Set-ItemProperty IIS:\Sites\$siteName -name applicationPool -value $siteName
    	$status = Get-WebAppPoolState -name $siteName
    
    	if ($status.Value -ne 'Stopped'){
    	   Write-Output ('Stopping Application Pool: {0}' -f $siteName)
    		Stop-WebAppPool -Name $siteName
    	}
    }
    

  3. Ensuring that the website and the App Pool are both stopped

    
    Import-Module WebAdministration
    $siteName = 'website.com';
    
    $exist = (Get-Website -name $siteName) -ne $null
    if($exist){
    	if(Test-Path IIS:\AppPools\$siteName){
    		$success = $false;
    		# timer in 60 sec 
    		$sec = 60;
    
    		do{
    			$iisState = (Get-WebsiteState -name $siteName).Value
    			$iisstopped = $iisState -eq "Stopped"
    			$poolState  = (Get-WebAppPoolState -name $siteName).Value
    			$poolstopped = $poolState -eq "Stopped"
    
    			Write-Output "Website state: $iisState"
    			Write-Output "AppPoolstate: $poolState "
    
    			$success = $iisstopped -and $poolstopped
    
    			if(!$success){
    			 Start-Sleep -s 1
    			 $sec = $sec - 1;
    			}
    		}
    		while (!$success -and $sec -gt 0)
    
    		if(!$success){
    		  throw "Can not stop IIS or App pool"
    		}
    	}
    }
    

  4. Setting the App Pool Identity user

    
    Import-Module WebAdministration
    $siteName = 'website.com';
    $appUser = 'Apppool_User';
    $appPassword = 'Apppool_User_Password';
    
    Set-ItemProperty IIS:\AppPools\$siteName -name processModel -value @{userName=$appUser;password=$appPassword;identitytype=3}
    
    

  5. Setting the SSL certificate and adding HTTPS binding to the website

    
    Import-Module WebAdministration
    $siteName = 'website.com';
    $fqdn = 'website.com';
    
    # get certificate by Subject
    $newCert =  dir Cert:\localmachine\My | where-Object {$_.subject -like "*website.com*"};
    # get certificate by thumbprint
    #$newCert =  dir Cert:\localmachine\My | where-Object {$_.Thumbprint -eq "1d486045ed3af7513b62ae43135421268bcde3dd"};
    
    
    $webbindings = Get-WebBinding -Name $siteName
    $webbindings
    
    
    $hasSsl = $webbindings | Where-Object { $_.protocol -like "*https*" }
    
    if($hasSsl)
    {
        Write-Output "An SSL certificate is already assigned."
    }
    else
    {
        Write-Output  "Applying TLS/SSL Certificate"
        New-WebBinding -Name $siteName -Port 443 -Protocol https -HostHeader $fqdn; #could add -IPAddress here if needed (and for the get below)
    	
    	$httpsBinding = Get-WebBinding -Name $siteName -Port 443 -Protocol "https" -HostHeader $fqdn;
    	
    	Write-Output $httpsBinding
    	
        $httpsBinding.AddSslCertificate($newCert.Thumbprint, "my")
    
        Write-Output  "`r`n`r`nNew web bindings"
        $webbindings = Get-WebBinding -Name $siteName
        $webbindings
    }
    

  6. Setting an environment variable

    
    Import-Module WebAdministration
    $siteName = 'website.com';
    $fqdn = 'website.com';
    
    $envVariables = "/system.applicationHost/applicationPools/add[@name='$(SITE_NAME)']/environmentVariables"
    $configSection = $envVariables + "/add[@name='ASPNETCORE_ENVIRONMENT']"
    $subsections=get-webconfiguration $configSection -PSPath iis:
    $subsections | foreach { $_.attributes | select name,value }
    
    if($subsections -eq $null){
    	$cmd='c:\windows\system32\inetsrv\appcmd.exe'
    &$cmd set config -section:system.applicationHost/applicationPools /+"[name='$(SITE_NAME)'].environmentVariables.[name='ASPNETCORE_ENVIRONMENT',value='$(ENVIRONMENT)']" /commit:apphost
    }
    

  7. Pinging the website to be sure whether it starts

    
    $KeepAliveUrl = "https://website.come/keepAlive"
    $InitTimeoutSec = 300
    $ReInitTimeoutSec = 60
    $ThirdTimeoutSec = 5
    
    Write-Host "KeepAliveUrl: " + $KeepAliveUrl;
    
    ## Enable TLS 1.2 to allow HTTPS communication
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    
    ## The first request after the deployment may take a long time 
    Write-Host "Request page to send a wake-up call and wait $InitTimeoutSec seconds to complete initialization"
    Invoke-WebRequest -Uri $KeepAliveUrl -TimeoutSec $InitTimeoutSec -UseBasicParsing
    
    ## Second request may still take a while as some initialization processes could
    ## have been started after first request (such as package installation via fridaycore), so smaller timeout here to make sure it is alright 
    Write-Host "Requestto send another wake-up call and wait $ReInitTimeoutSec seconds to complete"
    Invoke-WebRequest -Uri $KeepAliveUrl -TimeoutSec $ReInitTimeoutSec -UseBasicParsing
    
    ## Third and any further requests must be responded within 5s
    Invoke-WebRequest -Uri $KeepAliveUrl -TimeoutSec $ThirdTimeoutSec -UseBasicParsing
    

  8. Sometimes you need to install a .Net Core-related software with a specific version. Since you don`t have access to the server, you have to install it utilizing pipeline tasks. The easiest way to do it is to download the dotnet-install.ps1 script and use it for the installations.

    
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    
    $WebClient = New-Object System.Net.WebClient
    $WebClient.DownloadFile("https://dotnet.microsoft.com/download/dotnet-core/scripts/v1/dotnet-install.ps1","D:\dotnet-install.ps1")
    

  9. Installing the .NET Core Runtime version

    
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 
    d:\dotnet-install.ps1 -Runtime dotnet -Version 2.2.8
    

  10. Installing the ASP.NET Core Runtime version

    
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    d:\dotnet-install.ps1 -Runtime aspnetcore -Version 2.2.8
    

  11. Executing the SQL script

    
    $Query = "select * from [MyDatabase].[dbo].[MyTable]"
    Invoke-Sqlcmd -ConnectionString "$(ConnectionString)" -Query $Query | Out-File -FilePath "D:\temp\select_log.txt"
    

  12. Showing the logs by mask (for a specific date)

    
    $logpath = "c:\inetpub\wwwroot\website.com\logs"
    $today = Get-Date -Format "yyyyMMdd"
    $userDate = $(Date)
    
    if(![string]::IsNullOrEmpty($userDate)){
     $today = $userDate
    }
    
    if (Test-Path -Path $logpath ){
    	Get-ChildItem -Path $logpath
    
    	Get-ChildItem $logpath -Filter *$today*.txt | 
    	Foreach-Object {
    		Get-Content $_.FullName
    	}
    }
    

  13. Zipping and uploading the directory

    
    $OutputDir = "$(System.DefaultWorkingDirectory)\output"
    
    Compress-Archive -Path $OutputDir -DestinationPath $OutputDir -Force
    Write-host "##vso[task.uploadfile]$OutputDir.zip"

I hope you find these scripts useful and expand your toolset for project deployment.