Uploading files to Azure Applications (kudu)

I needed to copy some content to my azure application that the Build and deploy that I constructed for it wouldn’t need to do every deploy every time.   So my quest began on how do I upload files to an Azure application.  The most common and recognized way of uploading files to azure applications is through webdeploy. I didn’t think I needed to package up and use webdeploy so I sought out a way to do this with PowerShell.  This post is about that pursuit.

Thanks to this article most of the work was done Copy files to Azure Web App with PowerShell and Kudu API.  All I needed to do was to put a loop around my file upload and use Octavie van Haaften‘s scripts.

So I started with get-childitem -recurse “$downloadFolder\content”.  Now that I had my content in a variable called $files I can put this in a foreach loop and use Octavie van Haaften‘s  Upload-FileToWebapp.

During the upload of the files I need to determine if the file from my local disk is a File or Directory.  I used the following classes to determine this:

[System.IO.DirectoryInfo] &  [System.IO.FileInfo]

If the item was a directory then I had to make the upload location match the location on disk.  I did this through a little bit of replacement logic and used the $kudufolder as my variable to use for the upload function from Octavie.


$kudufolder = ((($file.FullName).Replace($uploadfrom,'Content'))`
.replace('\','/')).trimstart('/')
$kudufolder = "$kudufolder/"
Upload-FileToWebApp -resourceGroupName myresourcegroup`
-webAppName mywebapp -kuduPath $kudufolder

The same holds true for the upload of a file. The only difference between the file and the directory is the /. When you are uploading/creating a directory / to kudu means a directory.


$kudufile = ((($file.FullName).Replace($uploadfrom,'Content'))`
.replace('\','/')).trimstart('/')
Upload-FileToWebApp -resourceGroupName myresourcegroup`
-webAppName mywebapp -localPath $file.FullName -kuduPath $kudufile

Here is the full script in the foreach loop with each check for a directory or file.


$downloadfolder = 'c:\temp\myAzureStorage'

$uploadfrom = "$downloadfolder\Content"

$files = get-childitem -Recurse "$downloadfolder\Content"

foreach($file in $files)
{
if($file -is [System.IO.DirectoryInfo])
{
$kudufolder = ((($file.FullName).Replace($uploadfrom,'Content')).replace('\','/')).trimstart('/')
$kudufolder = "$kudufolder/"
Upload-FileToWebApp -resourceGroupName myresourcegroup -webAppName mywebapp -kuduPath $kudufolder
}
elseif($file -is [System.IO.FileInfo])
{
$kudufile = ((($file.FullName).Replace($uploadfrom,'Content')).replace('\','/')).trimstart('/')
Upload-FileToWebApp -resourceGroupName myresourcegroup -webAppName mywebapp -localPath $file.FullName -kuduPath $kudufile
}
}


I hope this helps someone
Until then keep Scripting
Thom


 

Azure File Storage Download

If you have an Azure account and you want to download files out of azure storage either individually or a whole folder. This script is about how I was able to do this with Powershell.

First we need to login to azure and get a storage context.  The StorageContext will require a key.


Add-AzureRmAccount -credential (get-credential) -tenantid yourid

$key = (get-azurermstorageAccountkey -resourcegroupname myresourcegroup -name mystorageaccountName | where-object{$Psitem.keyname -eq 'key1'}).value

$storageContext = New-AzureStorageContext -StorageAccountName "mystorage" -StorageAccountKey $key

Now that we have the storage context and key. Now we need to find the files that are in our AZURE File storage.


$content = get-azurestoragefile -storageaccountname "mystorage" -storageAccountkey $key

If we look at the contents of our $content variable we should see something similar to this:

files

Now that we have the content in a variable now we can begin the process of figuring out how to download each file. To start with to download a single file we need to use get-azureStorageFileContent 


$content = get-azurestoragefile -storageaccountname "mystorage" `

-storageAccountkey $key

get-azurestoragefilecontent -sharename "myshare" -path $content[0].uri.localpath `

-replace "$($content[0].share.name)/",'' -destination "c:\temp\" -context $storageContext

After much trial and error I found that in the object you get back from Azure there are two different Object types that you must check for:

Microsoft.WindowsAzure.Storage.File.FileDirectoryProperties

and the other type is:

Microsoft.WindowsAzure.Storage.File.CloudFile

By the class names you can see that one is a file and the other is a Directory.  With that in mind now I can put this in a function that recursively calls itself to get all the contents.


function Get-AzureFiles
{
param([string]$shareName = 'mystorage', [object]$storageContext, [string]$downloadFolder, [string]$path)
$content = get-azurestoragefile -sharename $sharename -Context $storagecontext -path $path| Get-AzureStorageFile

foreach($c in $content)
{
$Parentfolder = $c.uri.segments[($c.uri.segments.count -2)] -replace '/','\'

if(!(test-path $destination))
{mkdir $destination}
$p = $c.uri.LocalPath -replace "$($c.share.name)/" ,''
if(Get-AzureStorageFile -ShareName $c.share.name -path $p -Context $storageContext )
{
if($c.properties -is [Microsoft.WindowsAzure.Storage.File.FileDirectoryProperties])
{
$d = [Microsoft.WindowsAzure.Storage.File.CloudFileDirectory]::new($c.uri.AbsoluteUri)
#Get-AzureStorageFileContent -directory $d -ShareName $c.share.name -Destination "$destination$($c.name)" -Context $storageContext #-Path $p
$path = $d.Uri.LocalPath -replace "/$sharename/" , ""
$dest = $path -replace '/','\'
"$($c.name) is a directory -- getting $downloadfolder\$dest files"
if(!("$downloadfolder\$dest"))
{mkdir "$downloadfolder\$dest"}
Get-AzureFiles -shareName $shareName -storageContext $storageContext -path $path -downloadFolder "$downloadFolder\$dest"
}
elseif($c -is [Microsoft.WindowsAzure.Storage.File.CloudFile])
{
Write-Output "downloading --- $destination$($c.name)"
$destination = (($c.Uri.LocalPath -replace "/$sharename/" , "") -replace '/','\')
$dest = "$downloadFolder\$destination"
$dest
$de = $dest -replace $c.name, ""
if(!(test-path $de))
{
mkdir $de
}
if(!(test-path $dest))
{Get-AzureStorageFileContent -ShareName $c.share.name -Path $p -Destination $dest -Context $storageContext }# -WhatIf}
else
{
Write-Output "already downloaded --- $dest"
}
}
}
}
}

Now if we call the function with we’ll get a downloaded copy of all the folders from the location that you specify in azure to your local host:

get-AzureFiles -sharename “myShare” -storageContext $storageContext -downloadfolder “C:\temp\azurefiles”

There you go now you have your files in a directory from azure. Stay tuned for my next article where I’ll show you how to upload these same files to an Azure application (kudu).


I hope this helps someone
Until then keep Scripting
Thom


 

Updating Azure Alert Email

We have a number of Email’s setup for alerting that need to be changed. Rather than go to each alert and update their properties I chose to update each available alert in my subscriptions using PowerShell.  This post is about how I did that.

I will assume for the purposes of this post that you already are aware of the means to connect to Azure. If you aren’t familiar with that process see the article posted here.

The first thing I needed to figure out is how do I get my already configured alerts.  I chose to use the Cmdlet Get-AzureRmResource.  I then took the results of my query to find all the alerts in the current subscription context:

$alerts = get-AzureRmResource `
 | Where-Object{$_.resourcetype -like '*alert*'}

Now that I have all my resources that look like an alert I can now iterate through each and find the properties of each alert Get-AzureRmAlertRule:

foreach($alert in $alerts)
get-azureRmalertRule -Resourcegroup `
$alert.ResourceGroupName -Name $alert.Name
}
Properties : Microsoft.Azure.Management.Insights.Models.Rule
Tags : {[$type,
 Microsoft.WindowsAzure.Management.Common.Storage.CasePreservedDictionary,
 Microsoft.WindowsAzure.Management.Common.Storage], [hidden-link:/subscripti
 ons/xxx/resourceGroups/AzureTesting/provid
 ers/Microsoft.Web/serverfarms/EasyAuth, Resource]}
Id : /subscriptions/xxxx/resourceGroups/AzureTes
 ting/providers/microsoft.insights/alertrules/LongHttpQueue
Location : East US
Name : LongHttpQueue 

After some testing of this particular function I discovered that the extra switch of -DetailedOutput provided the detail I was looking for.

foreach($alert in $alerts)
get-azureRmalertRule -Resourcegroup `
$alert.ResourceGroupName -Name $alert.Name
}
Properties :
 Name: : LongHttpQueue EasyAuth
 Condition :
 DataSource :
 MetricName : HttpQueueLength
 ResourceId : /subscriptions/xxxxxxxx-xxxxxx-xxxxx-xxxxx-xxxxxxxxxx/re
 sourceGroups/AzureTesting/providers/Microsoft.Web/serverfarms/EasyAuth
 Operator : GreaterThan
 Threshold : 100
 Aggregation operator: Total
 Window size : 00:05:00
 Description : The HTTP queue for the instances of EasyAuth has a
 large number of pending requests.
 Status : Disabled
 Actions :
 SendToServiceOwners : True
 E-mails : 

Tags :
 $type :
 Microsoft.WindowsAzure.Management.Common.Storage.CasePreservedDictionary,
 Microsoft.WindowsAzure.Management.Common.Storage
 hidden-link:/subscriptions/xxxxxxxx-xxxxxx-xxxxx-xxxxx-xxxxxxxxxx/resourceGro
 ups/AzureTesting/providers/Microsoft.Web/serverfarms/EasyAuth:
 Resource
Id : /subscriptions/xxxxxxxx-xxxxxx-xxxxx-xxxxx-xxxxxxxxxx/resourceGroups/AzureTes
 ting/providers/microsoft.insights/alertrules/LongHttpQueue EasyAuth
Location : East US
Name : LongHttpQueue EasyAuth

Now I need to find out what the Email property was for this object I retrieved from the Get-AzureRmAlertRule.   If I inspect the object a little closer I find that there is a  sub Object called properties and then under that object I find another object that my Emails are associated to.   What I discovered through trial and error was that the Actions property was an array of settings.  The first item if set is the customEmails and whether or not an email should be sent upon alert activation (shown below).

PS PS:\azure> $t = get-azureRmalertRule -Resourcegroup `
'Azure Testing' -Name 'LongHttpQueue EasyAuth'
PS PS:\azure> $t.properties.Actions[0]

CustomEmails SendToServiceOwners
------------ -------------------
{} True

So this means if there are no emails set then the Array Count is Zero.  The other item that happens to be in the Action Object is whether or not a WebHook is set or not.  This can be seen by looking at the serviceuri in the actions object as shown below:

PS PS:\azure> $t =(get-azurermalertrule -name 'CPUHigh Dev' `
 -resourcegroup Dev -DetailedOutput)

PS PS:\azure> $t.properties.Actions | fl

Properties : {[$type, Microsoft.WindowsAzure.Management.Common.Storage.CasePreservedDict
 ionary`1[[System.String, mscorlib]],
 Microsoft.WindowsAzure.Management.Common.Storage]}
ServiceUri : https://s1events.azure-automation.net/webhooks?token=xxxx

CustomEmails : {email@email.com, email2@email.com}
SendToServiceOwners : True

On to how to change the email.  According to the blog article from Microsoft, you can only delete or add alert rules. I found this to be partially true.  In that if I already have an alert I can update it by just calling Add-AzurermMetricAlertRule.

Now to add email Items to the Add-AzurermMetricAlertRule you can do it two different ways:

The first way is use the Cmdlet Microsoft provides which creates an object of the precise thing you want and in the format the Add-AzurermMetricAlertRule expects:

$email = 'youremail@youremailServer.com'
$newEmailObj = new-azurermAlertRuleEmail -CustomEmails $email
add-azurermmetricalertrule -name $Name `
 -Location $Location -ResourceGroup $ResourceGroupName `
-operator ($alert.operator) -Threshold ($alert.threshold)`
 -TargetResourceId $alert.DataSource.ResourceUri`
 -MetricName $alert.DataSource.MetricName`
 -WindowSize $alert.WindowsSize`
 -TimeAggregationOperator $alert.TimeAggregation`
 -Description $targetResourceId.properties.Description`
 -Actions $newEmailObj

Or the other way you can do it is when you have the return result of alert already in an object you can use the .Add of the object to add an email to it.

$email = 'youremail@youremailServer.com'
$targetResourceId = (get-azurermalertrule -ResourceGroup `
$ResourceGroupName -Name $Name -DetailedOutput)
$actions = $targetResourceId.properties.Actions
 if($actions.count -eq 0)
 {
 $targetresourceId.properties.actions.add((`
new-azurermAlertRuleEmail -CustomEmails $email ))
 $targetresourceid.properties.actions`
[($targetresourceid.properties.actions.count -1)].SendToServiceOwners = $true
 $addedEmail = $true
 }
 else
 {
 $emailActions = $targetResourceId.properties.Actions.Count -1
 $emails = $actions[$emailActions].customemails
 if($emails -notcontains $email)
 {
 $targetResourceId.properties.actions[$emailActions].customemails.add($email)
 $addedEmail = $true
 }
 }

I chose to use the .add method as I’m doing this over and over again and it was to my advantage to use that method. Only when I have a case of there not being an alert ($actions.count -eq 0) do I use the New-AzureRmAlertRuleEmail.

I assume if there isn’t at least one item in $actions then it’s safe to add the email.

$emailActions = $targetResourceId.properties.Actions.Count -1
 $emails = $actions[$emailActions].customemails

I use $addedEmail to tell my function whether or not I need to add the email. This is because the the function will run these steps in a ForEach loop.

Now that I have a means to get the alert email and update it doing the converse is a matter of  changing the .Add method to a .Remove method and Bingo I have a add and a delete.  To see the entire script in action see this Gist. PS. I’m still working on the help. Will update the GIST as it is updated:

function Update-PAzureAlertEmail
{
<#
.SYNOPSIS
This function either adds or removes an email from Alerts in azure
.DESCRIPTION
This function will remove the enclosed email from all the alerts in an azure subscription.
If ADD is specified it will add the email to the all the alerts in an azure subscription.
When this function is called it will return an object of the items that were added or removed.
.EXAMPLE
Update-AzureAlertEmail -Subscription 'your subscription name' -email 'youremail@youremail.com'
This will go to every alert in the subscription passed and update with the youremail@youremail.com address.
default behaviour for -actions is to perform an Add.
.EXAMPLE
Update-AzureAlertEmail -email 'youremail@youremail.com'
This will go to every alert in all your subscriptions and add the email address that was specified.
.EXAMPLE
Update-AzureAlertEmail -email 'youremail@youremail.com' -action remove
This will go to every alert in all your subscriptions and remove the email address that was specified.
.EXAMPLE
Update-AzureAlertEmail -Subscription 'your subscription name' -email 'youremail@youremail.com' -action remove
This will go to every alert in the subscription passed and remove with the youremail@youremail.com address.
#>
[CmdletBinding()]
param
(
[Parameter(Position=0)]
[Object]
$Subscriptions = (Get-AzureRmSubscription),
[Parameter(Mandatory=$true)][string]$email,
[ValidateSet('Add', 'Remove')]
[string]$action = 'Add'
)
Begin
{
if((($Subscriptions | Get-Member).TypeName | Select-Object Unique) -eq 'Microsoft.Azure.Commands.Profile.Models.PSAzureSubscription')
{
$subs = $Subscriptions
}
else
{
$subs = get-AzureRmSubscription subscriptionname $subscriptions
}
$Updated = $null
$AlertsUpdated = @()
}
Process
{
ForEach ($SubName in $Subs)
{
$null = Set-AzureRmContext SubscriptionName ($SubName.SubscriptionName)
$alerts = get-AzureRmResource | Where-Object{$_.resourcetype -like '*alert*'}
#azure web tests https://azure.microsoft.com/en-us/blog/creating-a-web-test-alert-programmatically-with-application-insights/
foreach($alert in $alerts)
{
Write-Progress Activity Updating Status ('{0} ->' -f $action) PercentComplete ($alerts.IndexOf($alert)) CurrentOperation $alert.name
if($action -eq 'Add')
{
$Updated = Add-PAzureEmailAlertRule Subscriptionname $subname.subscriptionName ResourceGroupName $alert.ResourceGroupName location $alert.Location name $alert.Name email $email
$AlertsUpdated += $Updated
}
elseif($action -eq 'Remove')
{
$Updated = Remove-PAzureEmailAlertRule Subscriptionname $subname.subscriptionName ResourceGroupName $alert.ResourceGroupName location $alert.Location name $alert.Name email $email
$AlertsUpdated += $Updated
}
}
$AlertsUpdated
}
}
}
function Get-PAzureAlertEmail
{
<#
.SYNOPSIS
this function will get all alerts for the subscription passed.
.DESCRIPTION
This function will retreive all the alerts for the subscription passed. It will return an object with the Name of the alert
a property named emails that will contain the alerts for that email.
Resourcetype, ResourceGroupName, ResourceID, SubscriptionName, SubscriptionID and the location of the alert.
.EXAMPLE
get-pazurealertemail -Subscriptions 'azure testing'
This will get all the alerts for the azure testing subscription.
.EXAMPLE
get-pazurealertemail -Subscriptions 'azure testing' -email 'youremail@youremail.com' -find
This will search through all alerts to find an alert that has the email specified in it.
Each alert that is found it'll return them when the function completes.
#>
[CmdletBinding()]
param
(
[Parameter(Position=0)]
[Object]
$Subscriptions = (Get-AzureRmSubscription),
[string]$email,
[switch]$find
)
Begin
{
if((($Subscriptions | Get-Member).TypeName | Select-Object Unique) -eq 'Microsoft.Azure.Commands.Profile.Models.PSAzureSubscription')
{
$subs = $Subscriptions
}
else
{
$subs = get-AzureRmSubscription subscriptionname $subscriptions
}
}
Process
{
ForEach ($SubName in $Subs)
{
Set-AzureRmContext SubscriptionName ($SubName.SubscriptionName) | out-null
$alerts = get-AzureRmResource | Where-Object{$_.resourcetype -like '*alert*'}
#azure web tests https://azure.microsoft.com/en-us/blog/creating-a-web-test-alert-programmatically-with-application-insights/
foreach($alert in $alerts)
{
if($find -and $email -ne $null -or $email -ne '')
{$action = ('Searching for {0}' -f $email)}
else{$action = 'Gathering all alerts'}
Write-Progress Activity Searching Status ('{0} ->' -f $action) PercentComplete ($alerts.IndexOf($alert)) CurrentOperation $alert.name
#$targetResourceId = ((Get-AzureRmAlertRule -Name $alert[0].name -ResourceGroup $alert[0].ResourceGroupName).tags[1].key -replace 'hidden-link:','')
#https://docs.microsoft.com/en-us/azure/monitoring-and-diagnostics/insights-alerts-powershell#comments-container
$targetResourceId = get-azurermalertrule ResourceGroup $alert.ResourceGroupName Name $alert.Name DetailedOutput
$alertSetup = ($targetResourceId).properties.condition
$actions = $targetResourceId.properties.Actions
if($actions -eq 0)
{ $emails = ''}
else
{
$emailActions = $targetResourceId.properties.Actions.Count -1
$emails = $actions[$emailActions].customemails
}
if($find -and $email -ne $null -or $email -ne '')
{
if($emails -contains $email)
{
[PSCustomObject]@{
'Name' = $alert.Name
'Emails'= $emails
'ResourceType' = $alert.ResourceType
'ResourceGroupName' =$alert.ResourceGroupName
'ResourceId' = $alert.ResourceId
'SubscriptionName' = $subname.SubscriptionName
'SubscriptionId' = $Subname.SubscriptionId
'Location' = $alert.Location
} #PSCustomObject
}
}
else
{
[PSCustomObject]@{
'Name' = $alert.Name
'Emails'= $emails
'ResourceType' = $alert.ResourceType
'ResourceGroupName' =$alert.ResourceGroupName
'ResourceId' = $alert.ResourceId
'SubscriptionName' = $subname.SubscriptionName
'SubscriptionId' = $Subname.SubscriptionId
'Location' = $alert.Location
}
}
}
}
}
}
function Add-PAzureEmailAlertRule
{
<#
.SYNOPSIS
Adds an email for an alert in Azure.
.DESCRIPTION
This script will an an email to an existing alert. It will not add email addresses to Web Test type alerts or Smart Detection type alerts. It only operates on standard metric alerts.
.EXAMPLE
Add-PAzureAlertRule -subscriptionName 'your subscription' -resourcegroup 'resource group' -location 'US West' -name 'Name of the alert' -email 'emailToadd@youemailserver.com'
.EXAMPLE
Add-PAzureAlertRule -subscriptionName 'your subscription' -resourcegroup 'resource group' -location 'US West' -name 'Name of the alert' -email 'emailToadd@youemailserver.com'
#>
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Subscription Name that the alert is a part of')]
[ValidateNotNullOrEmpty()]
[string]$Subscriptionname,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Resource group name that the alert belongs to')]
[ValidateNotNullOrEmpty()]
[string]$ResourceGroupName,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Location in Azure where the alert is used.')]
[ValidateNotNullOrEmpty()]
[string]$location,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Actual name of the alert')]
[ValidateNotNullOrEmpty()]
[Alias('alertname')]
[string]$name,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Email Address that will be added to the alert')]
[ValidateNotNullOrEmpty()]
[string]$email
)
Begin
{
if((get-azurermcontext).Subscription.SubscriptionName -ne $Subscriptionname)
{
$null = set-azurermcontext Subscriptionname $Subscriptionname
}
}
Process
{
#$targetResourceId = ((Get-AzureRmAlertRule -Name $alert[0].name -ResourceGroup $alert[0].ResourceGroupName).tags[1].key -replace 'hidden-link:','')
#https://docs.microsoft.com/en-us/azure/monitoring-and-diagnostics/insights-alerts-powershell#comments-container
write-verbose Message "checking to see if an email is set. If not we'll set it and update the send to true"
$addedEmail = $false
$targetResourceId = (get-azurermalertrule ResourceGroup $ResourceGroupName Name $Name DetailedOutput)
#$alertSetup = ($targetResourceId).properties.condition
$actions = $targetResourceId.properties.Actions
$alertSetup = ($targetResourceId).properties.condition
if($actions.count -eq 0)
{
$targetresourceId.properties.actions.add((new-azurermAlertRuleEmail CustomEmails $email ))
$targetresourceid.properties.actions[($targetresourceid.properties.actions.count -1)].SendToServiceOwners = $true
$addedEmail = $true
}
else
{
$emailActions = $targetResourceId.properties.Actions.Count -1
$emails = $actions[$emailActions].customemails
if($emails -notcontains $email)
{
$targetResourceId.properties.actions[$emailActions].customemails.add($email)
$addedEmail = $true
}
}
#$sendtoServiceOwners = $actions[1].sendtoserviceowners
#$targetResourceId.properties.actions[$emailActions].CustomEmails
if($addedEmail)
{
if($targetResourceId.properties.Description -eq $null -or $targetResourceId.properties.Description -eq '')
{
$description = 'Added by Automation'
}
else
{
$description = $targetResourceId.properties.Description
}
Write-verbose Message ('updating {0} in ResourceGroup {1}' -f ($Name), ($ResourceGroupName))
write-verbose Message ('{0}' -f $Location)
if($alertSetup.threshold -ne $null -and $alertsetup.TimeAggregation -ne $null) #if operator is not present we assume it is a web test.
{
$found = Test-PAzureAlertSource Subscriptionname $subscriptionName ResourceGroupName $ResourceGroupName location $location name $name
#try{
# write-verbose "don't set the alert if we can't find the target"
# $null = get-azurermresource -resourceid $alertSetup.DataSource.ResourceUri
# $found = $true
# }
#catch{ $found = $false}
if($found)
{
add-azurermmetricalertrule name $Name Location $Location ResourceGroup $ResourceGroupName operator $alertSetup.operator Threshold ($alertSetup.threshold) TargetResourceId $alertSetup.DataSource.ResourceUri MetricName $alertSetup.DataSource.MetricName WindowSize $alertSetup.WindowsSize TimeAggregationOperator $alertsetup.TimeAggregation Description $description Actions $targetResourceId.properties.Actions #-Debug
}
else
{
$addedEmail = $false
write-warning Message ('cannot find ResourceID: {0}' -f $alertSetup.DataSource.ResourceUri)
}
}
elseif($alertsetup.TimeAggregation -eq $null)
{
write-verbose Message ('Time Aggregation is null cannot add email to {0}' -f $name)
$addedemail = $false
}
else
{
write-verbose Message ('An alert email cannnot be setup for {0} as it is a web test' -f $name)
#Add-AzureRmWebtestAlertRule -Name "webtestRule" -Location "East US" -ResourceGroup "Default-Web-EastUS" -WindowSize 00:05:00 -MetricName "metric" -TargetResourceUri ":/subscriptions/b67f7fec-69fc-4974-9099-a26bd6ffeda3/resourcegroups/Default-Web-WestUS/providers/microsoft.insights/webtests/leowebtestr1-webtestr1" -Description "Nice Webtest rule" -Failed 3
#add-AzureRmWebtestAlertRule -name $Name -Location $Location -ResourceGroup $ResourceGroupName -WindowSize $alertSetup.WindowSize -metricName $alertsetup.datasource.metricname -TargetResourceUri $alertSetup.DataSource.ResourceUri -Description $description -failed $alertSetup.FailedLocationCount #-Debug
$addedEmail = $false
}
}
if($addedEmail -eq $true)
{$targetResourceId}
else
{$targetresourceid =$null}
$targetresourceid
}
}
function Remove-PAzureEmailAlertRule
{
<#
.SYNOPSIS
Removes an email for an alert in Azure.
.DESCRIPTION
This script will remove an email from an existing alert. It will not remove email addresses from Web Test type alerts or Smart Detection type alerts. It only operates on standard metric alerts.
.EXAMPLE
Remove-PAzureAlertRule -subscriptionName 'your subscription' -resourcegroup 'resource group' -location 'US West' -name 'Name of the alert' -email 'emailToadd@youemailserver.com'
.EXAMPLE
Remove-PAzureAlertRule -subscriptionName 'your subscription' -resourcegroup 'resource group' -location 'US West' -name 'Name of the alert' -email 'emailToadd@youemailserver.com'
#>
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Subscription Name that the alert is a part of')]
[ValidateNotNullOrEmpty()]
[string]$Subscriptionname,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Resource group name that the alert belongs to')]
[ValidateNotNullOrEmpty()]
[string]$ResourceGroupName,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Location in Azure where the alert is used.')]
[ValidateNotNullOrEmpty()]
[string]$location,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Actual name of the alert')]
[ValidateNotNullOrEmpty()]
[Alias('alertname')]
[string]$name,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Email Address that will be removed from the alert')]
[ValidateNotNullOrEmpty()]
[string]$email
)
Begin
{
if((get-azurermcontext).Subscription.SubscriptionName -ne $Subscriptionname)
{
$null = set-azurermcontext Subscriptionname $Subscriptionname
}
}
Process
{
#$targetResourceId = ((Get-AzureRmAlertRule -Name $alert[0].name -ResourceGroup $alert[0].ResourceGroupName).tags[1].key -replace 'hidden-link:','')
#https://docs.microsoft.com/en-us/azure/monitoring-and-diagnostics/insights-alerts-powershell#comments-container
write-verbose Message "checking to see if an email is set. If not we'll set it and update the send to true"
$removedEmail = $false
$targetResourceId = (get-azurermalertrule ResourceGroup $ResourceGroupName Name $Name DetailedOutput)
#$alertSetup = ($targetResourceId).properties.condition
$actions = $targetResourceId.properties.Actions
$alertSetup = ($targetResourceId).properties.condition
$emailActions = $targetResourceId.properties.Actions.Count -1
$emails = $actions[$emailActions].customemails
if($emails -contains $email)
{
$targetResourceId.properties.actions[$emailActions].customemails.Remove($email)
$removedEmail = $true
}
#$sendtoServiceOwners = $actions[1].sendtoserviceowners
#$targetResourceId.properties.actions[$emailActions].CustomEmails
if($removedEmail)
{
if($targetResourceId.properties.Description -eq $null -or $targetResourceId.properties.Description -eq '')
{
$description = 'Added by Automation'
}
else
{
$description = $targetResourceId.properties.Description
}
Write-verbose Message ('updating {0} in ResourceGroup {1}' -f ($Name), ($ResourceGroupName))
write-verbose Message ('{0}' -f $Location)
if($alertSetup.threshold -ne $null -and $alertsetup.TimeAggregation -ne $null)
{
$found = Test-PAzureAlertSource Subscriptionname $subscriptionName ResourceGroupName $ResourceGroupName location $location name $name
#try{
# write-verbose "don't set the alert if we can't find the target"
# $null = get-azurermresource -resourceid $alertSetup.DataSource.ResourceUri
# $found = $true
# }
#catch{ $found = $false}
if($found)
{
add-azurermmetricalertrule name $Name Location $Location ResourceGroup $ResourceGroupName operator $alertSetup.operator Threshold ($alertSetup.threshold) TargetResourceId $alertSetup.DataSource.ResourceUri MetricName $alertSetup.DataSource.MetricName WindowSize $alertSetup.WindowsSize TimeAggregationOperator $alertsetup.TimeAggregation Description $description Actions $targetResourceId.properties.Actions #-Debug
$removedEmail = $true
}
else
{
$removedEmail = $false
write-warning Message ('cannot find ResourceID: {0}' -f $alertSetup.DataSource.ResourceUri)
}
}
elseif($alertsetup.TimeAggregation -eq $null)
{
write-verbose Message ('Time Aggregation is null cannot remove email from {0}' -f $name)
$removedEmail = $false
}
else
{
write-verbose Message ('An alert email cannnot be setup for {0} as it is a web test' -f $name)
#Add-AzureRmWebtestAlertRule -Name "webtestRule" -Location "East US" -ResourceGroup "Default-Web-EastUS" -WindowSize 00:05:00 -MetricName "metric" -TargetResourceUri ":/subscriptions/b67f7fec-69fc-4974-9099-a26bd6ffeda3/resourcegroups/Default-Web-WestUS/providers/microsoft.insights/webtests/leowebtestr1-webtestr1" -Description "Nice Webtest rule" -Failed 3
#add-AzureRmWebtestAlertRule -name $Name -Location $Location -ResourceGroup $ResourceGroupName -WindowSize $alertSetup.WindowSize -metricName $alertsetup.datasource.metricname -TargetResourceUri $alertSetup.DataSource.ResourceUri -Description $description -failed $alertSetup.FailedLocationCount #-Debug
$removedEmail = $false
}
}
if($removedEmail -eq $true)
{$targetResourceId}
else
{$targetresourceid = $null}
$targetresourceid
}
}
function Test-PAzureAlertSource
{
<#
.SYNOPSIS
Tests to see if the alert source is valid for an alert in Azure.
.DESCRIPTION
This function will return a $true if the alert source is valid and found in azure. If if it is found that the alert source is invalid then it'll will return a false.
.EXAMPLE
Test-PAzureAlertSource -subscriptionName 'your subscription' -resourcegroup 'resource group' -location 'USWest' -name 'Name of the alert'
.EXAMPLE
Test-PAzureAlertSource -subscriptionName 'your subscription' -resourcegroup 'resource group' -location 'USWest' -name 'Name of the alert'
#>
param
(
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Subscription Name that the alert is a part of')]
[ValidateNotNullOrEmpty()]
[string]$Subscriptionname,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Resource group name that the alert belongs to')]
[ValidateNotNullOrEmpty()]
[string]$ResourceGroupName,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Location in Azure where the alert is used.')]
[ValidateNotNullOrEmpty()]
[string]$location,
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
HelpMessage = 'Actual name of the alert')]
[ValidateNotNullOrEmpty()]
[Alias('alertname')]
[string]$name
)
Begin
{
if((get-azurermcontext).Subscription.SubscriptionName -ne $Subscriptionname)
{
$null = set-azurermcontext Subscriptionname $Subscriptionname
}
}
Process
{
$targetResourceId = (get-azurermalertrule ResourceGroup $ResourceGroupName Name $Name DetailedOutput)
#$alertSetup = ($targetResourceId).properties.condition
$valid = $false
try{
write-verbose Message "don't set the alert if we can't find the target"
$null = get-azurermresource resourceid $targetResourceId.properties.condition.datasource.resourceuri
$found = $true
}
catch{ $found = $false}
$found
}
}
function Remove-PAzureOrphanedAlerts
{
<#
.SYNOPSIS
Removes orphaned alerts from a subscription
.DESCRIPTION
This function will find all alerts in an azure subscription and remove them if the corresponding Asset that is being monitored no longer exists.
.EXAMPLE
Remove-PAzureOrphanedAlerts -Subscriptions 'Azure Testing'
Will return an object of all alerts that were removed.
.EXAMPLE
Remove-PAzureOrphanedAlerts
Will return an object of all alerts that have been removed for all subscriptions that the current user/entity can reach.
#>
[CmdletBinding(SupportsShouldProcess=$True, ConfirmImpact = 'Medium')]
param
(
[Parameter(Position=0)]
[Object]
$Subscriptions = (Get-AzureRmSubscription)
)
Begin
{
if((($Subscriptions | Get-Member).TypeName | Select-Object Unique) -eq 'Microsoft.Azure.Commands.Profile.Models.PSAzureSubscription')
{
$subs = $Subscriptions
}
else
{
$subs = get-AzureRmSubscription subscriptionname $subscriptions
}
$Updated = $null
$AlertsUpdated = @()
if ($ConfirmPreference -eq 'Low') {
<#
User:
* selected -Confirm
* has $Global:ConfirmPreference set to Low.
#>
$YesToAll = $false
}
else {
# No -Confirm, so we won't prompt the user…
$YesToAll = $true
}
# NoToAll is always $false – we want to give it a try…
$NoToAll = $false
}
Process
{
$action = 'Remove'
ForEach ($SubName in $Subs)
{
$null = Set-AzureRmContext SubscriptionName ($SubName.SubscriptionName)
$alerts = get-AzureRmResource | Where-Object{$_.resourcetype -like '*alert*'}
#azure web tests https://azure.microsoft.com/en-us/blog/creating-a-web-test-alert-programmatically-with-application-insights/
foreach($alert in $alerts)
{
$status = $null
Write-Progress Activity Updating Status ('from Subscription {1} {0} ->' -f $action,$subName.SubscriptionName) PercentComplete ($alerts.IndexOf($alert)) CurrentOperation $alert.name
if(!(Test-PAzureAlertSource Subscriptionname $SubName.SubscriptionName ResourceGroupName $alert.ResourceGroupName location $alert.location name $alert.name))
{
write-verbose "removing $($alert.name) from $($SubName.SubscriptionName) in Resource group: $($alert.ResourceGroupName) "
$targetResourceId = get-azurermalertrule ResourceGroup $alert.ResourceGroupName Name $alert.Name DetailedOutput
if($PSCmdlet.ShouldProcess("Removed the Alert – '$($alert.Name)'",
"Remove the alert '$($alert.Name)'?",
"Removing Alert" ))
{
if($Force -Or $PSCmdlet.ShouldContinue("Are you REALLY sure you want to remove '$($alert.Name)'?", "Removing '$($alert.Name)'", [ref]$YesToAll, [ref]$NoToAll))
{$status = Remove-AzureRmAlertRule ResourceGroup $alert.ResourceGroupName name $alert.name }
}
}
if($status -ne $null)
{$AlertsUpdated += $targetResourceId}
}
}
$AlertsUpdated
}
}
function Get-PAzureOrphanedAlerts
{
<#
.SYNOPSIS
Gets orphaned alerts from a subscription
.DESCRIPTION
This function will find all alerts in an azure subscription and return those orphaned alert names in an object.
.EXAMPLE
Get-PAzureOrphanedAlerts -Subscriptions 'Azure Testing'
Will return an object of all alerts that are orphaned.
.EXAMPLE
Remove-PAzureOrphanedAlerts
Will return an object of all alerts that are orphaned for each subscription that the user can get to.
#>
[CmdletBinding()]
param
(
[Parameter(Position=0)]
[Object]
$Subscriptions = (Get-AzureRmSubscription)
)
Begin
{
if((($Subscriptions | Get-Member).TypeName | Select-Object Unique) -eq 'Microsoft.Azure.Commands.Profile.Models.PSAzureSubscription')
{
$subs = $Subscriptions
}
else
{
$subs = get-AzureRmSubscription subscriptionname $subscriptions
}
$Updated = $null
$AlertsUpdated = @()
}
Process
{
ForEach ($SubName in $Subs)
{
$action = 'Get'
$null = Set-AzureRmContext SubscriptionName ($SubName.SubscriptionName)
$alerts = get-AzureRmResource | Where-Object{$_.resourcetype -like '*alert*'}
#azure web tests https://azure.microsoft.com/en-us/blog/creating-a-web-test-alert-programmatically-with-application-insights/
foreach($alert in $alerts)
{
$status = $null
Write-Progress Activity Getting Status ('from Subscription {1} {0} ->' -f $action,$subName.SubscriptionName) PercentComplete ($alerts.IndexOf($alert)) CurrentOperation $alert.name
if(!(Test-PAzureAlertSource Subscriptionname $SubName.SubscriptionName ResourceGroupName $alert.ResourceGroupName location $alert.location name $alert.name))
{
write-verbose "removing $($alert.name) from $($SubName.SubscriptionName) in Resource group: $($alert.ResourceGroupName) "
$targetResourceId = get-azurermalertrule ResourceGroup $alert.ResourceGroupName Name $alert.Name DetailedOutput
$status = $true
}
if($status -ne $null)
{$AlertsUpdated += $targetResourceId}
}
}
$AlertsUpdated
}
}
Function Format-OrphanedAlerts
{
<#
.SYNOPSIS
Takes an Orphand Alert Object and creates a flattened data set.
.DESCRIPTION
if Get-PAzureOrphanedAlerts is piped to this function it will return an object that is flattened. This object contains the following information:
Name of the alert
SourceResourceID = This is the thing that is being watched by the alert. Which is no longer in Azure (orphaned).
ResourceId = This is the resourceId that is associated with the alert.
emails = These are the email addresses that are associated with the Alert if present
Name : PulteWebSqlMasterLogIoPercent
SourceResourceId : /subscriptions/4a41ecf5-7683-49a5-8f1a-53ce28b8152a/resourceGroups/PulteWebProd/providers/Microsoft.Sql/servers/pulteweb/databases/PulteWebMaster
ResourceId : /subscriptions/4a41ecf5-7683-49a5-8f1a-53ce28b8152a/resourceGroups/PulteWebProd/providers/microsoft.insights/alertrules/PulteWebSqlMasterLogIoPercent
emails : youremailaddress@yourcompany.com
.EXAMPLE
Get-PAzureOrphanedAlerts -Subscriptions 'Azure Testing'
Will return an object of all alerts that are orphaned.
.EXAMPLE
Remove-PAzureOrphanedAlerts
Will return an object of all alerts that are orphaned for each subscription that the user can get to.
#>
param(
[object]$OrphanedAlerts
)
$formattedAlerts= $OrphanedAlerts | select @{n='Name';e={$_.properties.name}}, @{n='SourceResourceId';e={$_.properties.Condition.datasource.resourceuri}}, @{n = 'ResourceId';e={$_.id}}, @{n='emails';e={$_.properties.actions.customemails -join ','}}
$formattedAlerts
}
#select name,@{n="Emails";e={($_.emails) -join ','}},subscriptionName

I hope this helps someone out.

Until then keep scripting

thom