Hashtable not being read as hashtable in Azure Automation - powershell

We have a piece of code which works perfectly in Powershell ISE, however when we use the same code in Azure automation, it clears the tags.
$rgs = Get-AzureRmResourceGroup | Where-Object {$_.ResourceGroupName -like "*$rg*"}
foreach ($rg in $rgs)
{
$vms = Get-AzureRmVm -ResourceGroupName $rg.ResourceGroupName
$vms.ForEach({
$tags = $vm.Tags
$tags['ShutdownSchedule_AllowStop'] = "$False";
Set-AzureRmResource -ResourceId $_.Id -Tag $tags -Force -Verbose
})
}
What I've managed to establish so far is that the code Set-AzureRmResource -ResourceId $_.Id -Tag $tags -Force -Verbose does not see $tags as a hastable. I've done some debugging and I can see $tags = $vm.Tags is a hashtable.
I looked around on google and I've seen a few mentions of using [system.collections.hashtable] to specify it is a hashtable, but this is where my powershell is limited. I was hoping someone could point me in the right direction.
I've tried doingResourceId $_.Id -Tag [system.collections.hashtable]$tags but that didn't work.
What we are trying to do is change a tag value from true to false. The Key is Shutdown_AllowStop and the value is currently set to True.
Thanks in advance (and really hoping this makes sense) :)

The following script works for me.
$rgs = Get-AzureRmResourceGroup | Where-Object {$_.ResourceGroupName -like *$rg*"}
foreach ($rg in $rgs)
{
$vms = Get-AzureRmVm -ResourceGroupName $rg.ResourceGroupName
foreach ($vm in $vms)
{
$tags = $vm.Tags
foreach ($tag in $tags)
{
$tag['ShutdownSchedule_AllowStop'] = "$True";
Write-Output ("Showing VM's resource ID " + $vm.ID)
Write-output ("Show VM's tag "+[System.Collections.Hashtable]::new($tag))
$hash = [System.Collections.Hashtable]::new($tag)
$hash['ShutdownSchedule_AllowStop']
Set-AzureRmResource -ResourceId $vm.ID -Tag $hash -ApiVersion "2017-12-01" -Force -Verbose
}
}
}
But you need update Azure Power Shell version in Automation Account, if you don't do this, the script does not work.
This is my test result.

I think I see the issue. It works in the ISE because you defined $vm somewhere earlier in the session. Change it to $tags = $_.Tags and this should work for you.
Edit: So I took a look and the Get-AzureRmVm cmdlet returns an object where the Tags property is a horrid type when what they want in return is a hashtable. What it returns is:
[System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]]
Or Dictionary`2 for short if you run a .GetType() on it. What you can do to get around this is to explicitly declare your type when you define it:
[hashtable]$tags = $_.Tags

Related

Implementation of tags on resources in resource group using Powershell

I am trying to put tags on resources and resource group using Powershell.
The problem is it remove existing tags and add the new tags.
Powershell Script :
Get-AzureRmResourceGroup "testvsoRG" | Set-AzureRmResourceGroup -Tag #{Environment="UAT";Location="USA";DNS="www.xyz.com";Timezone="PST";Phase="Live"}
$resourceGroupName="testvsoRG"
$tags_to_apply=(Get-AzureRmResourceGroup -Name $resourceGroupName).tags
Find-AzureRmResource -ResourceGroupName $resourceGroupName | foreach
{
Set-AzureRmResource -ResourceId $_.resourceid -Tag $tags_to_apply -Force
}
What i want
Suppose I have 3 tags. I will provide resource group and it should add these 3 tags to the resource group and its resources. The resource group and its resource already have some tags in it. I want to keep those tags as it is and if my new tags matches the existing ones then it should update it.
For only resource group
$h = #{1="one";2="two";3="three"}
$resourceGroupName="testvsoRG"
$group=(Get-AzureRmResourceGroup -Name $resourceGroupName).tags
foreach ($key in $group.Keys)
{
if ($h.ContainsKey($key))
{
$group.Remove($key)
}
}
$group += $h
write-host $group
Error
System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.
Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry
Add tags in Azure Resource Group Resources
$resources = Get-AzureRmResource -ResourceGroupName $resourceGroupName
foreach ($r in $resources)
{
$resourcetags = (Get-AzureRmResource -ResourceId $r.ResourceId).Tags
foreach ($rkey in $resourcetags.Keys)
{
if ($h.ContainsKey($rkey))
{
$resourcetags.Remove($rkey)
}
}
$resourcetags += $h
Set-AzureRmResource -Tag $resourcetags -ResourceId $r.ResourceId -Force
}
Error
Get-AzureRmResource : Parameter set cannot be resolved using the specified named parameters.
If you use $tags_to_apply=(Get-AzureRmResourceGroup -Name $resourceGroupName).tags, the $tags_to_apply will be a hashtable.
The resource group and its resource already have some tags in it. I want to keep those tags as it is and if my new tags matches the existing ones then it should update it.
Per my understanding, you want to add new items in the hashtable, if the item is existing in the hashtable, update it. You could refer to my sample command, it works fine on my side.
$hashtable = #{1="one";2="two";3="three"}
$h = #{4="four";2="two2"}
foreach($key in $($hashtable.keys)){
if ($h.ContainsKey($key)){
$hashtable.Remove($key)
}
}
$hashtable += $h
Update:
It may caused by a mix of int and string because of the key name, I change the key name, and it works fine.
First, set the Tag.
$resourceGroupName = "resourceGroupName"
Set-AzureRmResourceGroup -Name $resourceGroupName -Tag #{tag1="one";tag2="two2";tag3="three"}
Second, set the Tag with new hash table.
$h = #{tag2="two";tag4="four"}
$resourceGroupName="resourceGroupName"
$group=(Get-AzureRmResourceGroup -Name $resourceGroupName).Tags
foreach($key in $($group.keys))
{
if(!$key){
if ($h.ContainsKey($key)){
$group.Remove($key)
}
}
}
$group += $h
Set-AzureRmResourceGroup -Name $resourceGroupName -Tag $group

Clears Tags in Set-AzureRmTag using Automation Account

I'm tying to change a value on a tag, using an automation script. The users will have a startup script, which will change the shutdown tag key from true to false.
When I set the tags individually using the script below it sets the tag value to false. The current setting is true.
When I use the automation script it wipes all the tags, however If I specify the vm in the script the automaton account works and changes the key value from false to true.
I can't see what I'm missing. This is from a webhook and is running as a powershell script, not a workflow.
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[object]$WebhookData
)
Write-Output "------------------------------------------------"
Write-Output "`nConnecting to Azure Automation"
$Connection = Get-AutomationConnection -Name AzureRunAsConnection
Add-AzureRMAccount -ServicePrincipal -Tenant $Connection.TenantID `
-ApplicationId $Connection.ApplicationID -CertificateThumbprint $Connection.CertificateThumbprint
$RunbookVersion = "0.0.17"
$timeStartUTC = (Get-Date).ToUniversalTime()
Write-Output "Workflow started: Runbook Version is $RunbookVersion"
Write-Output "System time is: $(Get-Date)"
Write-Output "`nGetting tagged resources"
Write-Output "------------------------------------------------"
$ResourceGroupFilter = ""
$SupportedEnvironments = "DEV, Test, PREProd, Prod"
$isWebhookDataNull = $WebhookData -eq $null
Write-Output "Is webhook data null ? : $($isWebhookDataNull)"
# If runbook was called from Webhook, WebhookData will not be null.
If ($WebhookData -ne $null) {
# Collect properties of WebhookData
$WebhookName = $WebhookData.WebhookName
$WebhookHeaders = $WebhookData.RequestHeader
$WebhookBody = $WebhookData.RequestBody
$body = $WebhookBody | ConvertFrom-Json
$UserEmail = $body.user.email
Write-Output "Runbook started from webhook '$WebhookName' by '$($body.user.email)' for environment '$($body.environment)'"
Write-Output "Message body: " $WebhookBody
}
else {
Write-Error "Runbook mean to be started only from webhook."
}
If ($body.environment.ToUpper() -eq 'DEV') {
$ResourceGroupFilter = 'The-DEV-RG'
}
if ($ResourceGroupFilter -eq "") {
Exit 1
}
if($VMRG -eq ''){
Write-Output "No resource groups matched for selected environment. Webhook cant progress further, exiting.."
Write-Error "No resource groups matched for selected environment. Webhook cant progress further, exiting.."
Exit 1
}
$rgs = Get-AzureRmResourceGroup | Where-Object {$_.ResourceGroupName -like "*$rg*"}
foreach ($rg in $rgs)
{
$vms = Get-AzureRmVm -ResourceGroupName $rg.ResourceGroupName
$vms.ForEach({
$tags = $_.Tags
$tags['ShutdownSchedule_AllowStop'] = "$False";
Set-AzureRmResource -ResourceId $_.Id -Tag $tags -Force -Verbose
})
}
ForEach ($vm in $vms) {
Start-AzureRmVM -Name $vm.Name -ResourceGroupName $vm.ResourceGroupName -Verbose
}
Thanks in advance :)
The root reason is your local Azure Power Shell is latest version, but in Azure automation account, it is not latest version. I test in my lab, older version does not support this.
You need upgrade Azure Power Shell version. More information about this please see this answer.

Assign Tags using Powershell and reading from CSV

I have a bit of a tricky one.
I have a csv file which has the following headers. I'm trying to assign the vmname in the csv the tags as mentioned in the csv under the relevant header (hope that makes sense).
vmname, resourcegroup, size, costcenter, displayname etc
When I run the first 20 lines it produces the azure text file as expected.
However if I run the second block lines 23 onwards to assign the tags to the vm it adds the key values but not the actual value and shows as blank in tags on the portal through the gui.
I'm not sure what I'm doing wrong and wondered if anyone can see what I'm doing, the only thing I think it could be the $vm = $_.vmname, but can't see how.
The code I have is below
$csv = import-csv "C:\temp\book.csv"
$csv | foreach-object {
$first =$_.VMName
$Second =$_.ResourceGroup
$size=$_.Size
$t1= $_.Costcenter
$t2= $_.Displayname
$t3= $_.Environment
$t4= $_.Project
$t5= $_.Role
$t6= $_.Template
$t7= $_.DSC
$t8= $_.Schedule
$t9= $_.AppID
$t10= $_.Service
$t11= $_.REF
$t12= $_.OS
$t13= $_.Zone
"The VM is $first, the RG is $second, the size is $size and tags $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10, $t11, $t12, $t13" | out-file C:\temp\Azure.txt -append
}
##the above works fine
Select-AzureRmSubscription -SubscriptionId "xxxxxx-xxxx-xxxx-xxxx-xxxxxxx"
$csv = import-csv "C:\temp\book.csv"
$vm = "$_.VMName"
$tags = (Get-AzureRmResource -ResourceGroupName "RG01" -ResourceType "Microsoft.Compute/virtualMachines" -Name "$VM").Tags
$csv | ForEach-Object {
$first =$_.VMName
$t1= $_.Costcenter
$t2= $_.Displayname
$t3= $_.Environment
$t4= $_.Project
$t5= $_.Role
$t6= $_.Template
$t7= $_.DSC
$t8= $_.Schedule
$t9= $_.AppID
$t10= $_.Service
$t11= $_.REF
$t12= $_.OS
$t13= $_.Zone
$tags += #{
Costcenter="$t1"
Displayname="$t2"
Environment="$t3"
Project="$t4"
Role="$t5"
Template="$t6"
DSC="$t7"
Schedule="$t8"
AppID="$t9"
Service="$t10"
DREF="$t11"
OS="$t12"
Zone="$t13"
}
}
Set-AzureRmResource -ResourceGroupName "RG01" -Name "$VM" -Tag $tags -ResourceType "Microsoft.Compute/virtualMachines" -verbose
Thanks in advance :)
It looks like you're over-complicating this task a bit.
If I understand correctly, what you want to do (in pseudo-code) is:
foreach $VM in $CSVfile
Retrieve existing tags for $VM
Add csv values to existing tags
Set new tag set on $VM in azure
This should be pretty straightforward:
Select-AzureRmSubscription -SubscriptionId "xxxxxx-xxxx-xxxx-xxxx-xxxxxxx"
$csv = import-csv "C:\temp\book.csv"
$csv | ForEach-Object {
# Retrieve existing tags
$tags = (Get-AzureRmResource -ResourceGroupName "RG01" -ResourceType "Microsoft.Compute/virtualMachines" -Name $_.VMName).Tags
# Add new value pairs from CSV
$tags += #{
Costcenter = $_.Costcenter
Displayname = $_.Displayname
Environment = $_.Environment
Project = $_.Project
Role = $_.Project
Template = $_.Template
DSC = $_.DSC
Schedule = $_.Schedule
AppID = $_.AppID
Service = $_.Service
DREF = $_.REF
OS = $_.OS
Zone = $_.Zone
}
# Update resource with new tag set
Set-AzureRmResource -ResourceGroupName "RG01" -Name $_.VMName -Tag $tags -ResourceType "Microsoft.Compute/virtualMachines" -verbose
}
If any of the tag names from your CSV file already exist as tags on the resource, make sure you add them manually, one at a time:
Select-AzureRmSubscription -SubscriptionId "xxxxxx-xxxx-xxxx-xxxx-xxxxxxx"
$csv = import-csv "C:\temp\book.csv"
$csv | ForEach-Object {
# Retrieve existing tags
$tags = (Get-AzureRmResource -ResourceGroupName "RG01" -ResourceType "Microsoft.Compute/virtualMachines" -Name $_.VMName).Tags
# Define new value pairs from CSV
$newTags = #{
Costcenter = $_.Costcenter
Displayname = $_.Displayname
Environment = $_.Environment
Project = $_.Project
Role = $_.Project
Template = $_.Template
DSC = $_.DSC
Schedule = $_.Schedule
AppID = $_.AppID
Service = $_.Service
DREF = $_.REF
OS = $_.OS
Zone = $_.Zone
}
# Add new tags to existing set (overwrite conflicting tag names)
foreach($tagName in $newTags.Keys){
$tags[$_] = $newTags[$_]
}
# Update resource with new tag set
Set-AzureRmResource -ResourceGroupName "RG01" -Name $_.VMName -Tag $tags -ResourceType "Microsoft.Compute/virtualMachines" -verbose
}

Check If Azure Resource Group Exist - Azure Powershell

I'm trying to verify if ResourceGroup exist or not so i thought that following code should return true or false, but it doesn't output anything.
$RSGtest = Find-AzureRmResource | Format-List ResourceGroupName | get-unique
$RSGtest -Match "$myResourceGroupName"
Why am I not getting any output?
Update:
You should use the Get-AzResourceGroup cmdlet from the new cross-plattform AZ PowerShell Module now. :
Get-AzResourceGroup -Name $myResourceGroupName -ErrorVariable notPresent -ErrorAction SilentlyContinue
if ($notPresent)
{
# ResourceGroup doesn't exist
}
else
{
# ResourceGroup exist
}
Original Answer:
There is a Get-AzureRmResourceGroup cmdlet:
Get-AzureRmResourceGroup -Name $myResourceGroupName -ErrorVariable notPresent -ErrorAction SilentlyContinue
if ($notPresent)
{
# ResourceGroup doesn't exist
}
else
{
# ResourceGroup exist
}
try this
$ResourceGroupName = Read-Host "Resource group name"
Find-AzureRmResourceGroup | where {$_.name -EQ $ResourceGroupName}
I am a PS newbie and I was looking for a solution to this question.
Instead of searching directly on SO I tried to investigate on my own using PS help (to get more experience on PS) and I came up with a working solution.
Then I searched SO to see how I compared to experts answers.
I guess my solution is less elegant but more compact. I report it here so others can give their opinions:
if (!(Get-AzResourceGroup $rgname -ErrorAction SilentlyContinue))
{ "not found"}
else
{"found"}
Explanation of my logic: I analyzed the Get-AzResourceGroup output and saw it's either an array with found Resource groups elements or null if no group is found. I chose the not (!) form which is a bit longer but allows to skip the else condition. Most frequently we just need to create the resource group if it doesn't exist and do nothing if it exists already.
I was also looking for the same thing but there was a additional condition in my scenario.
So I figured it out like this. To get the scenario details follow
$rg="myrg"
$Subscriptions = Get-AzSubscription
$Rglist=#()
foreach ($Subscription in $Subscriptions){
$Rglist +=(Get-AzResourceGroup).ResourceGroupName
}
$rgfinal=$rg
$i=1
while($rgfinal -in $Rglist){
$rgfinal=$rg +"0" + $i++
}
Write-Output $rgfinal
Set-AzContext -Subscription "Subscription Name"
$createrg= New-AzResourceGroup -Name $rgfinal -Location "location"
Had a similar challenge, I solved it using the script below:
$blobs = Get-AzureStorageBlob -Container "dummycontainer" -Context $blobContext -ErrorAction SilentlyContinue
## Loop through all the blobs
foreach ($blob in $blobs) {
write-host -Foregroundcolor Yellow $blob.Name
if ($blob.Name -ne "dummyblobname" ) {
Write-Host "Blob Not Found"
}
else {
Write-Host "bLOB already exist"
}
}

Update IIS 6 WebSite Credentials via powershell

I am trying to write a script that will loop through all of my local IIS website and update their physical path credentials whenever I'm forced to update my domain password.
The following works... the first time you run it...
function Set-Site-Credentials(
$SiteElement,
$Credentials
){
$SiteElement.virtualDirectoryDefaults.userName = "$($Credentials.Domain)\$($Credentials.UserName)"
$SiteElement.virtualDirectoryDefaults.password = $Credentials.Password
$SiteElement | Set-Item -Force
}
After running this, I noticed that the following properties also get set
$SiteElement.userName #Same as was set earlier on .virtualDirectoryDefaults
$SiteElement.password #Same as was set earlier on .virtualDirectoryDefaults
Subsequently, anytime I try to update the credentials using the code above, these two properties remain unchanged, and the changes don't take affect in IIS.
So the result is:
$SiteElement.userName #Unchanged
$SiteElement.password #Unchanged
$SiteElement.virtualDirectoryDefaults.userName #New value
$SiteElement.virtualDirectoryDefaults.password #New value
And the IIS site still shows the old username in the UI and the credentials fail.
So naturally I tried setting those extra 2 properties in my update function:
function Set-Site-Credentials(
$SiteElement,
$Credentials
){
$SiteElement.userName = "$($Credentials.Domain)\$($Credentials.UserName)"
$SiteElement.password = $Credentials.Password
$SiteElement.virtualDirectoryDefaults.userName = "$($Credentials.Domain)\$($Credentials.UserName)"
$SiteElement.virtualDirectoryDefaults.password = $Credentials.Password
$SiteElement | Set-Item -Force
}
The code throws no errors or warnings, but the end result is the same, those 2 extra properties remain unchanged.
I am using the following code to get "$SiteElement"
$sites = Get-ChildItem IIS:\Sites
$sites | Foreach-Object { Set-Site-Credentials -SiteElement $_ -Credentials $newCredentials }
Also, at the end of the script I restart IIS using this command:
Restart-Service W3SVC
Ugh, finally found a command that works. All in all I've tried 4 different variation of the same thing from different example around the interwebz, all of which only work the first time. But this command updates properly on subsequent changes:
function Set-Site-Credentials(
$SiteElement,
$Credentials
){
Set-WebConfiguration -Filter "$($SiteElement.ItemXPath)/application[#path='/']/virtualDirectory[#path='/']" -Value #{userName="$($Credentials.Domain)\$($Credentials.UserName)"; password="$($Credentials.Password)"}
}
The full script
param (
[switch]$All,
[switch]$AllPools,
[switch]$AllSites,
[string]$AppPool,
[string]$Site
)
Import-Module WebAdministration
function Set-AppPool-Credentials(
$AppPoolElement,
$Credentials
){
Set-ItemProperty $AppPoolElement.PSPath -name processModel -value #{userName="$($Credentials.Domain)\$($Credentials.UserName)";password="$($Credentials.Password)";identitytype=3}
}
function Set-Site-Credentials(
$SiteElement,
$Credentials
){
Set-WebConfiguration -Filter "$($SiteElement.ItemXPath)/application[#path='/']/virtualDirectory[#path='/']" -Value #{userName="$($Credentials.Domain)\$($Credentials.UserName)"; password="$($Credentials.Password)"}
}
$newCredentials = (Get-Credential).GetNetworkCredential()
$appPools = Get-ChildItem IIS:\AppPools
$sites = Get-ChildItem IIS:\Sites
if($All -or $AllPools){
$appPools | Foreach-Object { Set-AppPool-Credentials -AppPoolElement $_ -Credentials $newCredentials }
}
elseif($AppPool){
$poolElement = ($appPools | Where-Object { $_.name -eq $AppPool })
Set-AppPool-Credentials -AppPoolElement $poolElement -Credentials $newCredentials
}
if($All -or $AllSites){
$sites | Foreach-Object { Set-Site-Credentials -SiteElement $_ -Credentials $newCredentials }
}
elseif($Site){
$siteElement = ($sites | Where-Object { $_.name -eq $Site })
Set-Site-Credentials -SiteElement $siteElement -Credentials $newCredentials
}
Restart-Service W3SVC