Connect Azure Automation to Azure DevOps as script repository

Azure Automation provides seamless integration with Azure DevOps and other Version Control Systems. Having change-tracking and backup & restore capabilities is a blast!

This first blog covers workstation preparation, creation and initial setup of Azure Automation and the connection to Azure DevOps.

Permissions to created resources in Azure, administrative permissions on Azure DevOps organization and permissions to create Enterprise Applications in EntraID are needed to perform the next steps.

Programs on your computer

The following free programs shall be installed on your computer. I strongly recommend to follow the installation order as mentioned.

  1. PowerShell 7.x : To be downloaded from https://github.com/PowerShell/PowerShell/releases . – Python is also supported, but not explained further 😉

  2. GIT, which is required as underlying mechanism for the source control system. Available for Windows computers on https://git-scm.com/download/win .

    – Reboot your computer – 

  3. Visual Studio Code / VSCode as development environment. To be found at https://code.visualstudio.com/download

  4. Lastly the following VSCode extension as they simplify working with Azure:
    PowerShell, Azure Account, Azure Automation, Azure Resource Manager, Azure   Resources, Azure Storage, Git History, GitLens

Create an Azure DevOps Project

To keep things organized and to avoid conflicts I suggest created a dedicated project in Azure DevOps. Ensure that you select private repository.

If desired, create new repository named “Runbooks”. Click on “initialize”.

Next, GIT on your workstation needs to be initiated. Open a GIT CMD and type the following commands:


git config --global user.email "azrxyyyy@xyy.onmicrosoft.com"

git config --global user.name "FirstName LastName"

Create an Azure Automation Account

Search for the Marketplace and look for the Automation Account to proceed:

Specify the subscription, the resource group, a fitting name and the region in which the resource shall be created.

Then activate the Managed by selecting “System assigned”. – This is important to later grant permissions to Azure Resources or the M365 Graph.

Finally, the account is ready.

Connecting Azure Automation Account with Azure DevOps

In the Automation Account Settings, select Source control and click on Add.

Next, after choosing Azure DevOps confirm the grant the permission consent.

In the parameter windows, specify the Azure DevOps project previously created, a branch “main” and activate both option for Runbook publications etc.

Last, the connection is established.

Choosing between Azure Automation and Function Apps

Azure Automation and Function Apps offer server-less script execution.

This post explains key differences between Azure Automation and Function Apps, concludes with an opinion on the preferred choice for a Scheduled Task replacement.

Comparing site by site

Usage / Use cases

Both allow the execution of scheduled jobs which themselves are based on scripts. PowerShell is supported on both options.

Audience

Manageability

Complexity Level

Various Differences

Conclusion

In my opinion is Azure Automation the best fit to be the better alternative to tradition Scheduled Task. Version Control, Change-Tracking, Security, Monitoring, Redundancy options and much more are reason for it.

Evolve from Scheduled Tasks to Azure Automation or Function Apps

Scheduled Tasks have been around since the early days of Windows Server. The task scheduler is the inbuilt component used to plan and execute jobs. Typically, System administrators use them to automate reoccurring activities which are defined in scripts.

While Scheduled Tasks are robust and easy to use, they have a couple of shortcomings.

This blog explains the advantages of Azure Automation or Function App over Scheduled tasks.

Looking on Scheduled Tasks and their opponents in Azure

Storage / Location

Execution


Redundancy

Change Tracking / Versioning

Monitoring

Security

Costs

Round Up

To conclude, Azure Runbooks and Function Apps outperform the traditional Scheduled Tasks in many aspects.

Visualize Aruba Wireless Infrastructure with Squared Up

Aruba Wireless technology is one of the market leaders. Squared Up can bring in visibility and reduces the MTTR*.

This post explains by example how to extract, transform, and visualize Aruba data by using the PowerShell and Squared Up.

Introduction

Aruba (part of HP Enterprise) provides wireless LAN solutions for small offices to large enterprise networks. Mobility-controller are used to manage access points centrally. By using master-controller a management hierarchy can be setup by keeping one point of administration.

Network specialists either use command line or the web interface. Automation and programmatic access are given via common REST API.

Squared Up in version 5.3 (Jan 2022) offers many possibilities to retrieve data and visualize it in no time.

Most versatile capability is the native integration of PowerShell. It allows any kind of data transformation or aggregation before passing it the dashboard engine.

Prerequisites

At least Aruba OS 8.5 on the controller and PowerShell 5.1 on the Squared Up server are suggested.

A local user account “aruba_monitor” with password needs to be created on the controller.

Dry Run

Before coming to the details, try if retrieving Aruba information from the Squared Up server works.

Getting all switches:

The script below retrieves all switches ( controller ) and adds state information that is needed for Squared Up. The information is stored in a CSV file which allows instant showing of the information. Use Scheduled Tasks to run the script regularly (e.g. every 5 minutes).

#region PREWORK Disabling the certificate validations
add-type -TypeDefinition @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[Net.ServicePointManager]::CertificatePolicy = New-Object -TypeName TrustAllCertsPolicy
#endregion PREWORK


$API_BASE_URI    = 'https://your-aruba-master-url'
$DeviceUsername  = 'aruba_monitor'
$DevicePassword  = 'your-password'


$session = Invoke-RestMethod -Uri "${API_BASE_URI}/v1/api/login" -Method Post -Body "username=$DeviceUsername&password=$DevicePassword" -SessionVariable api_session

$sessionID = $session._global_result.UIDARUBA
 

$allSwitches =  Invoke-RestMethod -Uri "${API_BASE_URI}/v1/configuration/showcommand?command=show+switches+all&UIDARUBA=${sessionID}" -WebSession $api_session
$switches = $allSwitches.'All Switches'


$switchList = New-Object -TypeName System.Collections.ArrayList

foreach ($sw in $switches) {

  $stateTmp = ($sw.Status -split ' ')[0]
  $state = 'Healthy'
  if ($stateTmp -ieq 'up') {
    $state = 'Healthy'
  } elseif ($stateTmp -ieq 'down') {
    $state = 'Critical'
  } else {
    $state = 'Warning'
  }

  $swObj = [pscustomobject]@{
    ConfigID           = $sw.'Config ID'
    ConfigSyncTimeSec  = $sw.'Config Sync Time (sec)'
    ConfigurationState = $sw.'Configuration State'
    name               = $sw.Name
    IP                 = $sw.'IP Address'
    Status             = $stateTmp
    Location           = $sw.Location
    Model              = $sw.Model
    SiteCode           = $sw.Name.Substring(0,5).ToUpper()
    Type               = $sw.Type
    State              = $state
  }

  $null = $switchList.Add($swObj)


} #end foreach ($sw in $switches) 


$switchList | Export-Csv -NoTypeInformation -Path C:\temp\aruba_switchlist.csv -Force


#log off – Important as otherwise all sessions will be blocked!
Invoke-RestMethod -Uri "${API_BASE_URI}/v1/api/logout" -Method Post -SessionVariable api_session 

Getting all access points:

For convenience, simply extend the script above ( just before log off section ) the following lines which export all access points. – Some meta information is appended, too:

$allAccessPoints = Invoke-RestMethod -Uri "${API_BASE_URI}/v1/configuration/showcommand?command=show+ap+database&UIDARUBA=${sessionID}" -WebSession $api_session
$accessPoints = $allAccessPoints.'AP Database'


$accessPointList = New-Object -TypeName System.Collections.ArrayList


foreach ($ap in $accessPoints) {

  $switchName = $switches | Where-Object {$_.'IP Address' -eq $ap.'Switch IP'} | Select-Object -ExpandProperty Name
  $siteCode = $switchName.Substring(0,5).ToUpper()

  if ($ap.Status.Length -gt 6) {
    $uptime = ($ap.Status -split ' ')[1]
    $tmpTime = $uptime -replace '[a-z]',''
    $tmpTime2 = $tmpTime -split ':'
    $tmpTime3 = New-TimeSpan -Days $tmpTime2[0] -Hours $tmpTime2[1] -Minutes $tmpTime2[2]
    $lastboot = (Get-Date) - [timespan]$tmpTime3
  } else {
    $uptime = 0 
    $lastboot = 0
  }

  $stateTmp = ($ap.Status -split ' ')[0]
  $state = 'Healthy'
  if ($stateTmp -ieq 'up') {
    $state = 'Healthy'
  } elseif ($stateTmp -ieq 'down') {
    $state = 'Critical'
  } else {
    $state = 'Warning'
  }


  $apObj = [pscustomobject]@{
    apType     = "Model:" + $ap.'AP Type'
    Flags      = $ap.Flags
    Group      = $ap.Group
    Name       = $ap.Name
    IP         = $ap.'IP Address'
    Status     = $stateTmp
    UpTime     = $uptime
    LastBoot   = $lastboot
    SwIP       = $ap.'Switch IP'
    SiteCode   = $siteCode
    SwitchName = $switchName
    State      = $state
  }

  $null = $accessPointList.Add($apObj)

} # end foreach ($ap in $accessPoints)


$accessPointList  | Export-Csv -NoTypeInformation -Path C:\temp\aruba_accesspointlist.csv -Force

Verifying results

Running the scripts should result in having two CSV files in C:\Temp. If this is the case, proceed.

E.g. aruba_switchlist.csv

"ConfigID","ConfigSyncTimeSec","ConfigurationState","name","IP","Status","Location","Model","SiteCode","Type","State"
"1872","0","UPDATE SUCCESSFUL","ArubaMM","10.1.11.168","up","Building1.floor1","ArubaMM-VA","DEL","master","Healthy"
"1872","10","UPDATE SUCCESSFUL","ARUBA02","172.16.2.240","up","Building1.floor1","Aruba7030","ATS","MD","Healthy"

E.g. aruba_accesspointlist.csv

"apType","Flags","Group","Name","IP","Status","UpTime","LastBoot","SwIP","SiteCode","SwitchName","State"
"Model:224","U2","default","40:e3:d6:c5:cd:f8","10.1.21.104","Down","0","0","10.1.1.221","DELIN","DELINARUBA04","Critical"
"Model:515","2","AEDUB-apgrp","AEDUBAP02","172.31.20.21","Up","9d:4h:22m:2s","2/3/2022 11:11:24 AM","172.31.2.90","AEDUB","AEDUBARUBA01","Healthy"

Overview Dashboard

Create the overview dashboard that is shown at the beginning. Create a new dashboard.

Section: Controller Health

Start with PowerShell (Status – Block):

Enter the script and apply filtering in needed:

Finalize by adding a sub label:

Section: Access Points Health

Start with PowerShell (Donut):

Enter the script and apply filter is required:

Apply custom color formatting:

Section: Unhealthy Access Points

Start with PowerShell (Status -Icons)

Enter the script and filter if needed:

Pick the proper label:

Section: Access Points – Recent Boots

Start with PowerShell (Grid):

Enter the script:

Configure the grid columns:

Section: Access Points Types

Start with PowerShell (bar graph):

Enter the script and filter if needed:

Set the label property:

Enable multiple colors:

Conclusion

This example has shown you how to easily create a dashboard of your Aruba infrastructure.

Btw: Squared Up Community Edition is free! – Find it on: https://squaredup.com/community-edition/

One more thing …

Another great use case is using the PowerShell (Status Icon) tile in combination with your building layout. – See your access points health and know exactly where they are:

* MTTR = Mean Time To Repair

Squared Up dashboard for locked Active Directory Users – Only in PowerShell

Introduction

Squared Up’s Web-API tile allows it to integrate information from any web-service that returns JSON data.

With Polaris, a free and open source framework it is possible to build web-services just in PowerShell.

This example explains the steps to create web-service in Polaris which returns locked out user information and how to integrate them nicely in Squared Up.

su-Dashboard
Locked User Dashboard

Requirement

  • A windows server that will host your Polaris web-service
  • On that server PowerShell version 5.1
  • Active Directory Users & Computer and its module installed ( part of the RSAT )
  • Administrative permissions on to install Polaris (Install-Module -Name Polaris)
  • Create an empty directory adsvc insight of C:\Program Files\WindowsPowerShell\Modules\Polaris
  • Open the Windows firewall to allow incoming connection to the port you specify, here 8082.
  • Limit this port to only accept request from your Squared Up server.
  • NSSM – the Non-Sucking Service Manage to run your web-service script as a service.
    https://nssm.cc/

Realization

The solution consists of two PowerShell scripts. The first one exports locked user information into a JSON file. It needs to be scheduled via Task Scheduler to provide up-to-date information for the dashboard. It would be also possible to extract locked user information on each dashboard load, but that would be very slow.

Export Script

Create a directory C:\ScheduledTasks and copy the following lines into text file. Name it Export-ADLockedAndExpiredUsers.ps1. Place the following content into it:

Import-module ActiveDirectory

$jsonFilePath = 'C:\ScheduledTasks\SquaredUpExports\ADLockedAndExpiredUsers.json'

# storing raw active directory information in ArrayList
$rawLockedUersList = New-Object -TypeName System.Collections.ArrayList

Search-ADAccount -LockedOut | Select-Object -Property Name,SamAccountName,Enabled,PasswordNeverExpires,LockedOut,`
                                    LastLogonDate,PasswordExpired,DistinguishedName | ForEach-Object {
                                        if ($_.Enabled) {
                                            $null = $rawLockedUersList.Add($_)
                                        }
}


# helper function to get account lock out time
Function Get-ADUserLockedOutTime {

    param(
        [Parameter(Mandatory=$true)]
        [string]$userID
    )

    $time = Get-ADUser -Identity $_.SamAccountName -Properties AccountLockoutTime `
        | Select-Object @{Name = 'AccountLockoutTime'; Expression = {$_.AccountLockoutTime | Get-Date -Format "yyyy-MM-dd HH:mm"}}

    $rtnValue = $time | Select-Object -ExpandProperty AccountLockoutTime

    $rtnValue

} #End Function Get-ADUserLockedOutTime


# main function that sorts and formats the output to fit better in the dashboard
Function Get-ADUsersRecentLocked {

    param(
        [Parameter(Mandatory=$true)]
        [System.Collections.ArrayList]$userList
    )

    $tmpList = New-Object -TypeName System.Collections.ArrayList
    
    $tmpList = $userList | Sort-Object -Property LastLogonDate -Descending
    $tmpList = $tmpList  | Select-Object -Property Name,`
                    @{Name = 'UserId' ; Expression = { $_.SamAccountName }}, `
                    @{Name = 'OrgaUnit' ; Expression = { ($_.DistinguishedName -replace('(?i),DC=\w{1,}|CN=|\\','')) -replace(',OU=',' / ')} }, `
                    Enabled,PasswordExpired,PasswordNeverExpires, `
                    @{Name = 'LastLogonDate'; Expression = { $_.LastLogonDate | Get-Date -Format "yyyy-MM-dd HH:mm" }}, `
                    @{Name = 'AccountLockoutTime'; Expression = { (Get-ADUserLockedOutTime -userID $_.SamAccountName) }}

    $tmpList = $tmpList | Sort-Object -Property AccountLockoutTime -Descending                    
    
    # adding a flag character for improved visualization (alternating)
    $rtnList   = New-Object -TypeName System.Collections.ArrayList    
    $itmNumber = $tmpList.Count
    
    for ($counter = 0; $counter -lt $itmNumber; $counter ++) {

        $flack = ''
        if ($counter % 2) { 
            $flack = ''
        } else {
            $flack = '--'
        }

        $userProps = @{
            UserId               = $($flack + $tmpList[$counter].UserId)
            OrgaUnit             = $($flack + $tmpList[$counter].OrgaUnit)
            Enabled              = $($flack + $tmpList[$counter].Enabled)
            PasswordExpired      = $($flack + $tmpList[$counter].PasswordExpired)
            PasswordNeverExpires = $($flack + $tmpList[$counter].PasswordNeverExpires)
            LastLogonDate        = $($flack + $tmpList[$counter].LastLogonDate)
            AccountLockoutTime   = $($flack + $tmpList[$counter].AccountLockoutTime)
        }

        $userObject = New-Object -TypeName psobject -Property $userProps
        
        $null = $rtnList.Add($userObject)        
        Write-Host $userObject

    } #end for ()          

    $rtnList

} #End Function Get-ADUsersRecentLocked

if (Test-Path -Path $jsonFilePath) {
    Remove-Item -Path $jsonFilePath -Force
}


# exporting result to a JSON file and storing it on $jsonFilePath
Get-ADUsersRecentLocked -userList $rawLockedUersList  | ConvertTo-Json | Out-File $jsonFilePath -Encoding utf8 

Publish Script

Create a directory C:\WebSrv and create an empty text file in it. Rename the file Publish-ADData.ps1. Place the following content into it. This directory contains your web-service.

Import-Module -Name Polaris
$polarisPath = 'C:\Program Files\WindowsPowerShell\Modules\Polaris'

# runs every time the code runs and ensure valid JSON output
$middleWare = @"
    `$PolarisPath = '$polarisPath\adsvc'
    if (-not (Test-path `$PolarisPath)) {
        [void](New-Item `$PolarisPath -ItemType Directory)
    }
    if (`$Request.BodyString -ne `$null) {
        `$Request.Body = `$Request.BodyString | ConvertFrom-Json
    }
    `$Request | Add-Member -Name PolarisPath -Value `$PolarisPath -MemberType Noteproperty    
"@

New-PolarisRouteMiddleware -Name JsonBodyParser -ScriptBlock ([scriptblock]::Create($middleWare)) -Force


# the Get route is launched every time the web-service is called 
New-PolarisGetRoute -Path "/adsvc" -ScriptBlock {
        
    $rawLockedUersList = New-Object -TypeName System.Collections.ArrayList    
    

    $rawData  = Get-Content -Path 'C:\ScheduledTasks\SquaredUpExports\ADLockedAndExpiredUsers.json'
    $jsonData = $rawData | ConvertFrom-Json
    
    if ($jsonData.Count -ne 0) {
        $jsonData | ForEach-Object {
            $null = $rawLockedUersList.Add($_)
        }
    }
     
    $reportTime = Get-item -Path C:\ScheduledTasks\SquaredUpExports\ADLockedAndExpiredUsers.json `
                    | Select-Object -ExpandProperty LastWriteTime | Get-Date -Format "yyyy-MM-dd HH:mm"
    
    $maxNoOfUsers = $null    
    $maxNoOfUsers = $request.Query['maxNoOfUsers']   

    $getReportTime = 'no'
    $getReportTime = $request.Query['getReportTime'] 
    
    $getLockedUserCount = 'no'
    $getLockedUserCount = $request.Query['getLockedUserCount'] 
    
    #if getLockedUserCoutn is yes then return number of locked users
    if ($getLockedUserCount -eq 'yes') {
        $noProps = @{ 'number' = $rawLockedUersList.Count }
        $noObj = New-Object psobject -Property $noProps           
        $response.Send(($noObj | ConvertTo-Json))        
    } 

    #if maxNumber is a number than return locked user information
    if ($maxNoOfUsers -match '\d') {
        $rawLockedUersList = $rawLockedUersList | Select-Object -First $maxNoOfUsers
        $response.Send(($rawLockedUersList | ConvertTo-Json))                
    } 

    #if getReportTime is yes then the time of export will be returned    
    if  ($getReportTime -eq 'yes') {
        
        $tmProps = @{
            'Time' = $reportTime
            'DisplayName' = [System.TimezoneInfo]::Local | Select-Object -ExpandProperty DisplayName
        }
        $tmObj = New-Object psobject -Property $tmProps           
        $response.Send(($tmObj | ConvertTo-Json))        
    }

} -Force

Start-Polaris -Port 8082 

#Keep Polaris running
while($true) {
    Start-Sleep -Milliseconds 10
} 

Configure your web-service to run as a service

Download NSSM and store the nssm.exe in C:\WebSrv . Run the following PowerShell line to convert Publish-ADData.ps1 into a service. – Use ISE or VSCode.

function Install-Service {
    Param(
        [string]$nssmPath = '.',
        [string]$Name,
        [string]$Description,
        [string]$Executable,
        [string]$Arguments
    )

    $nssm = Join-Path -Path $nssmPath -ChildPath 'nssm.exe'
    & $nssm install $name $executable $arguments
    $null = & $nssm set $name Description $description
    Start-Service $name
}

Install-Service -Name WWW-Polaris-ADUserLockData -Description 'PowerShell HTTP API Service - Serves ADUserLockedInfo via REST' -Executable powershell.exe -Arguments '-ExecutionPolicy Bypass -Command C:\WebSrv\Publish-ADData.ps1'

The result can be found in the Windows Services:

Testing

From your Squared Up server, start a web browser and query the web-service.

Locked User Account
Report Time
Locked User Details

Dashboard Building

Add a provider

In Squared Up, switch to System and add a new WEB API provider.  The URL is the one of your Polaris web-service.

Adding WEB-API provider

Add the Dashboard

Create a new dashboard and name it Locked User Info for example. Add a Web-API tile to show the locked user count information.

1-      Locked User Counter

choose the recently created Provider
name the URL with ?getLockedUserCount=yes
skip headers & data with next
specify the key path with .number
optimally, specify the size, complete with done.

2 – Locked Users Details

select WEB API (Grid)
skip the scoping with next
choose the provider previously created
as a URL type in ?MaxNoOfUsers=20
as a key path name propery
edit the columns via edit
place the following code to ensure alternating colors and the removal of “–“
finalize with done

3 – Report time

Add a last WEB-API tile type grid and specify the following URL ?getReportTime=yes

Summery

This walk through shows how to integrate own data via Polaris as REST web-service.

Hope it is useful to one of you.

Feedback is appreciated