Pester test doesn't fail with the Array missing values - azure

We are writing Pester test for testing the Azure Resource group to contain certain tags. Following is the script and unfortunately the Pester test is not reporting any failure even after a particular resource group we are checking doesn't contain some of the Tags (from the Array defined). The Pester tests just passes and I am not sure what is that we are doing wrong here.
$resourceGroupName ='DemoRG03032021'
$listOfTags = #('BUSINESS-OWNER','COST-CENTER','LIFECYCLE1', 'APPLICATION','PROJECT-CODE','TECHNICAL-OWNER','BUDGET-CODE')
$checkTags = $false
Describe "Resource Group" {
Context "$resourceGroupName" {
$resourceGroup = Get-AzResourceGroup -Name $resourceGroupName
foreach ($tagName in $listOfTags)
{
It "$($resourceGroup.ResourceGroupName) has a $tagName as tag" {
$resourceGroup.tags.keys -contains $tagName | Should -Be $true
}
}
}
}

In v5 you can now also do it like this which is a bit more readable in my opinion:
BeforeDiscovery {
$listOfTags = #('BUSINESS-OWNER', 'COST-CENTER', 'LIFECYCLE1', 'APPLICATION', 'PROJECT-CODE', 'TECHNICAL-OWNER', 'BUDGET-CODE')
}
BeforeAll {
$resourceGroupName = 'DemoRG03032021'
$resourceGroup = Get-AzResourceGroup -Name $resourceGroupName
}
Describe "Resource Group" -ForEach $listOfTags {
It "$($resourceGroup.ResourceGroupName) has a $_ as tag" {
$resourceGroup.tags.keys -contains $_ | Should -Be $true
}
}
Edit: Putting the answer to your follow up question here as it's a lot more readable.
This is how I would personally organize the code you posted in the comments. I think adding another logical block makes sense, actually. If you put your other It statements inside of the block running with -Foreach then you would run every new test once for every tag in $listOfTags as well which is probably not what you want.
BeforeDiscovery {
$listOfTags = #('BUSINESS-OWNER', 'COST-CENTER', 'LIFECYCLE1', 'APPLICATION', 'PROJECT-CODE', 'TECHNICAL-OWNER', 'BUDGET-CODE')
}
Describe "Resource Group Tests" {
BeforeAll {
$resourceGroupName = 'TestResourceGroup203122021'
$resourceGroupLocation = 'eastus22222'
$resourceGroup = Get-AzResourceGroup -Name $resourceGroupName
}
Context "Resource Group Tags" -ForEach $listOfTags {
It "$($resourceGroup.ResourceGroupName) has a $_ as tag" {
$resourceGroup.tags.keys -contains $_ | Should -Be $true
}
}
Context "Resource Group Attributes" {
It "Resource Group $($resourceGroup.ResourceGroupName) Exists" {
$resourceGroup | Should -Not -BeNullOrEmpty
}
It "$($resourceGroup.ResourceGroupName) Location is $resourceGroupLocation" {
$($resourceGroup.Location) | Should -Be $resourceGroupLocation
}
}
}
Here is another way to think about it. If you wrote the following:
Foreach ($tag in $listOfTags){
Write-Host 'do this thing for each tag'
Write-Host 'do this thing once'
}
Imagine each Write-Host is your It statement. You don't want that second statement inside of the same context as the other, because you don't want it to run once for every value in $listOfTags. You logically separate it with a new Describe or Context block.

Related

How to get the list of azure servers having Auto-Shutdown disabled using PowerShell?

I want to get the list of azure servers having auto-shutdown disabled on them, I have the below script but the issue with the script is that it gets the list of RG's under the Subscription GUID but repeats the output after every loop.
Import-AzureRmContext -Path "$PSScriptRoot\AzureProfile.json"
Select-AzureRmSubscription -SubscriptionId {subscriptionId}
[array]$ResourceGroupArray = Get-AzureRMVm | Select-Object -Property ResourceGroupName, Name, VmId
foreach ($resourceGroup in $ResourceGroupArray){
$targetResourceId = (Get-AzureRmVM -ResourceGroupName $resourcegroup.ResourceGroupName -Name $resourceGroup.Name).Id
$shutdownInformation = (Get-AzureRmResource -ResourceGroupName $resourcegroup.ResourceGroupName -ResourceType Microsoft.DevTestLab/schedules -Expandproperties).Properties
Write-Host "ID: " $targetResourceId
$shutdownInformation
The output for each VM is displayed in the following format,
What I want is simple, I want the VM name and its status of Auto-shutdown to be displayed on the screen so that its easy for me to find out which all VM have auto-shutdown currently disabled on them.
Any help related to this would be helpful.
You just need to get the microsoft.devtestlab/schedules resource ID using:
/subscriptions/{subscriptionId}/resourceGroups/{rgName}/providers/microsoft.devtestlab/schedules/shutdown-computevm-{vmName}
Then iterate over all your VMs using Get-AzVM, Get the microsoft.devtestlab/schedules resource using Get-AzResource, then output VM name and status into a table using Format-Table.
$subscriptionId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Set-AzContext -SubscriptionId $subscriptionId
& {
foreach ($vm in Get-AzVM) {
try {
$shutdownResource = Get-AzResource `
-ResourceId "/subscriptions/$subscriptionId/resourceGroups/$($vm.ResourceGroupName)/providers/microsoft.devtestlab/schedules/shutdown-computevm-$($vm.Name)" `
-ErrorAction Stop
[PSCustomObject]#{
VMName = $vm.Name
ShutdownStatus = $shutdownResource.Properties.status
}
}
catch {
[PSCustomObject]#{
VMName = $vm.Name
ShutdownStatus = $_.Exception.Message
}
}
}
} | Format-Table -AutoSize
To set the context to the correct subscription, we can use Set-AzContext.
The above however is using the latest Az modules. You can do the same using the equivalent AzureRm modules.
$subscriptionId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Set-AzureRmContext -SubscriptionId $subscriptionId
& {
foreach ($vm in Get-AzureRmVM) {
try {
$shutdownResource = Get-AzureRmResource `
-ResourceId "/subscriptions/$subscriptionId/resourceGroups/$($vm.ResourceGroupName)/providers/microsoft.devtestlab/schedules/shutdown-computevm-$($vm.Name)" `
-ErrorAction Stop
[PSCustomObject]#{
VMName = $vm.Name
ShutdownStatus = $shutdownResource.Properties.status
}
}
catch {
[PSCustomObject]#{
VMName = $vm.Name
ShutdownStatus = $_.Exception.Message
}
}
}
} | Format-Table -AutoSize
Although I do recommend moving to the Az module since support for AzureRm is ending December 2020. You can read the documentation for more information about this.
The above code should give you an output similar to the following
VMName ShutdownStatus
------ --------------
vm1 Enabled
vm2 Disabled
Update
The Call operator & is used here to run the for loop as a script block. You can read more about this in about_Script_Blocks.
Try something like this to get the auto-shutdown status of all VMs. Instead of trying to get the schedules inside the loop, get all the ones in the subscription and match them based on the VM's full resource Id.
[array]$VMArray = Get-AzureRMVm | Select-Object -Property ResourceGroupName, Name, VmId, Id
$ShutdownInformation = (Get-AzureRmResource -ResourceType Microsoft.DevTestLab/schedules -Expandproperties).Properties
foreach($vm in $VMArray) {
$ShutdownStatus = "Not Configured"
$Schedule = $ShutdownInformation | Where-Object { $_.targetResourceId -eq $vm.Id } | Select -First 1
if($Schedule -ne $null) {
$ShutdownStatus = $Schedule.status
}
Write-Host $vm.VmId $ShutdownStatus
}

How to delete the ADFPipeline which is having the references Forcefully

I'm actually some automation for my ADF. As a part of that, I'm trying to delete all the ADF V2 pipelines. The problem is my pipelines having many references with different pipelines itself.
$ADFPipeline = Get-AzDataFactoryV2Pipeline -DataFactoryName $(datafactory-name) -ResourceGroupName $(rg)
$ADFPipeline | ForEach-Object { Remove-AzDataFactoryV2Pipeline -ResourceGroupName $(rg) -DataFactoryName $(datafactory-name) -Name $_.name -Force }
And most of the time I get the error like
The document cannot be deleted since it is referenced by "blabla"
I understand the error that it saying some references and cannot be deleted. However, when I tried the same deletion in the azure portal, irrespective of the reference I can able to delete. So I want to find a way that whether it possible to tell that Powershell even though it's having a reference delete it forcefully
Any other inputs much appreciated!
I run into the same issue, found out that it's rather complicated to build the whole dependency graph out of the pipeline's Activities property.
As a working solution (powershell):
function Remove-Pipelines {
param (
[Parameter(Mandatory=$true)]
[AllowEmptyCollection()]
[AllowNull()]
[System.Collections.ArrayList]$pipelines
)
if($pipelines.Count -gt 0) {
[System.Collections.ArrayList]$plsToProcess = New-Object System.Collections.ArrayList($null)
foreach ($pipeline in $pipelines) {
try {
$removeAzDFCommand = "Remove-AzDataFactoryV2Pipeline -dataFactoryName '$DataFactoryName' -resourceGroupName '$ResourceGroupName' -Name '$($pipeline.Name)' -Force -ErrorAction Stop"
Write-Host $removeAzDFCommand
Invoke-Expression $removeAzDFCommand
}
catch {
if ($_ -match '.*The document cannot be deleted since it is referenced by.*') {
Write-Host $_
$plsToProcess.Add($pipeline)
} else {
throw $_
}
}
}
Remove-Pipelines $plsToProcess
}
}
Here is the complete solution for clearing the whole DF: "trigger","pipeline","dataflow","dataset","linkedService"
Param(
[Parameter(Mandatory=$true)][string] $ResourceGroupName,
[Parameter(Mandatory=$true)][string] $DataFactoryName
)
$artfTypes = "trigger","pipeline","dataflow","dataset","linkedService"
function Remove-Artifacts {
param (
[Parameter(Mandatory=$true)][AllowEmptyCollection()][AllowNull()][System.Collections.ArrayList]$artifacts,
[Parameter(Mandatory=$true)][string]$artfType
)
if($artifacts.Count -gt 0) {
[System.Collections.ArrayList]$artToProcess = New-Object System.Collections.ArrayList($null)
foreach ($artifact in $artifacts) {
try {
$removeAzDFCommand = "Remove-AzDataFactoryV2$($artfType) -dataFactoryName '$DataFactoryName' -resourceGroupName '$ResourceGroupName' -Name '$($artifact.Name)' -Force -ErrorAction Stop"
Write-Host $removeAzDFCommand
Invoke-Expression $removeAzDFCommand
}
catch {
if ($_ -match '.*The document cannot be deleted since it is referenced by.*') {
Write-Host $_
$artToProcess.Add($artifact)
} else {
throw $_
}
}
}
Remove-Artifacts $artToProcess $artfType
}
}
foreach ($artfType in $artfTypes) {
$getAzDFCommand = "Get-AzDataFactoryV2$($artfType) -dataFactoryName '$DataFactoryName' -resourceGroupName '$ResourceGroupName'"
Write-Output $getAzDFCommand
$artifacts = Invoke-Expression $getAzDFCommand
Write-Output $artifacts.Name
Remove-Artifacts $artifacts $artfType
}
The same approach can be adapted for "Set-AzDataFactoryV2Pipeline" command as well.
It worth to mention that along with dependencies tracking, Remove/Set artifact's sequence should be right (because of cross artifacts' dependencies).
For Set - "linkedService","dataset","dataflow","pipeline","trigger"
For Remove - "trigger","pipeline","dataflow","dataset","linkedService"
Hello and thank you for the question. According to the Remove-AzDataFactoryV2Pipeline doc, the -Force flag simply skips the confirmation prompt. It does not actually 'Force' the deletion in spite of errors.
Since you are already doing automation, might I suggest leveraging the error message to recursively attempt to delete the referencing pipeline. $error[0] gets the most recent error.
(Pseudocode)
try_recurse_delete( pipeline_name )
do_delete(pipeline_name)
if not $error[0].contains("referenced by " + pipeline_name)
then return true
else
try_recurse_delete( get_refrencer_name($error[0]) )
Given that pipeline dependencies can be a many-to-many relationship, subsequent pipelines in your for-each loop might already be deleted by the recursion. You will have to adapt your code to react to 'pipeline not found' type errors.

Replace Find-AzureRmResource with Get-AzureRmResource in AzureRM for Tagging Resources

I have a script that will apply all tags in a resource group to the child resources in the group. The script uses Find-AzureRmResource which has been depricated and removed from the newest modules. It says it has been replaced with Get-AzureRmResource, however I am unable to get it working properly with replacing with that. I get the error:
"Get-AzureRmResource : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the
input and its properties do not match any of the parameters that take pipeline input."
Here is the original script that used to work:
$rgname = "rg123"
$group = Get-AzureRmResourceGroup $rgname
if ($group.Tags -ne $null) {
$resources = $group | Find-AzureRmResource
foreach ($r in $resources)
{
$resourcetags = (Get-AzureRmResource -ResourceId $r.ResourceId).Tags
foreach ($key in $group.Tags.Keys)
{
if (($resourcetags) -AND ($resourcetags.ContainsKey($key))) { $resourcetags.Remove($key) }
}
$resourcetags += $group.Tags
Set-AzureRmResource -Tag $resourcetags -ResourceId $r.ResourceId -Force
}
}
here is the find-azurermresource I am trying to replace with:
$resources = $group | Get-AzureRmResource -ResourceGroupName $rgname
I have tried variations with -ResourceType as well, but still get the same error that it cannot take pipeline inputs. Is there away to get get this line working again with the replaced cmdlet Get-AzureRmResource?
You can immediatly use the following, no need to use Get-AzureRmResourceGroup:
$resources = Get-AzureRmResource -ResourceGroupName $rgname
This will get all resources from that specific group.

Azure Powershell Tagging VM's from CSV file

I'm quite new to Powershell scripting. I'm trying to generate tags for azure vm's from a CSV file.
Inside the CSV I have the following column headings:
VMName
Application
SubCat
Environment
AppOwner
Location
I've got a test CSV which literally has the following data in it:
MattTestVM, TestApp, TestSubapp, Dev, Matt, UK South
I'm not sure what i've put wrong in my code to get it to add the tags.
Code
#Set Credentials
$cred = Get-credential
# Sign-in with Azure account credentials
add-azurermaccount -credential $cred
# Select Azure Subscription
$subscriptionId = (Get-AzureRmSubscription | Out-GridView -Title "Select an Azure Subscription ..." -PassThru).SubscriptionId
#Select specified subscription ID
Select-AzureRmSubscription -SubscriptionId $subscriptionId
$InputCSVFilePath = "C:\test\Tagging1.csv"
#Start loop
foreach ($eachRecord in $InputCSVFilePath)
{
$VMName = $eachrecord.VMName
$Application = $eachrecord.Application
$SubCat = $eachrecord.SubCat
$Environment = $eachrecord.Environment
$AppOwner = $eachrecord.AppOwner
$Location = $eachrecord.Location
$r = Get-AzureRmResource -ResourceId $ResourceId -ErrorAction Continue
if($r -ne $null)
{
if($r.tags)
{
# Tag - Application
if($r.Tags.ContainsKey("Application"))
{
$r.Tags["Application"] = $Application
}
else
{
$r.Tags.Add("Application", $Application)
}
# Tag - SubCat
if($r.Tags.ContainsKey("subCat"))
{
$r.Tags["subCat"] = $subCat
}
else
{
$r.Tags.Add("subCat", $subCat)
}
# Tag - Environment
if($r.Tags.ContainsKey("Environment"))
{
$r.Tags["Environment"] = $Environment
}
else
{
$r.Tags.Add("Environment", $Environment)
}
# Tag - AppOwner
if($r.Tags.ContainsKey("AppOwner"))
{
$r.Tags["AppOwner"] = $AppOwner
}
else
{
$r.Tags.Add("AppOwner", $AppOwner)
}
# Tag - Location
if($r.Tags.ContainsKey("Location"))
{
$r.Tags["Location"] = $Location
}
else
{
$r.Tags.Add("Location", $Location)
}
#Setting the tags on the resource
Set-AzureRmResource -Tag $r.Tags -ResourceId $r.ResourceId -Force
}
else
{
#Setting the tags on a resource which doesn't have tags
Set-AzureRmResource -Tag #{ Application=$Application; subCat=$subCat; Environment=$Environment; AppOwner=$AppOwner; Location=$Location } -ResourceId $r.ResourceId -Force
}
}
else
{
Write-Host "Resource Not Found with Resource Id: " + $ResourceId
}
}
Error message
Get-AzureRmResource : Cannot validate argument on parameter 'ResourceId'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At line:10 char:43
+ $r = Get-AzureRmResource -ResourceId $ResourceId -ErrorAction Co ...
+ ~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Get-AzureRmResource], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.GetAzureResourceCmdlet
OK, first thing's first: You can't do this without the ResourceGroupName, and the best way to add it in your case is as a column in your csv.
Secondly, everybody starts somewhere so never apologize. :) I commented about most of the issues I fixed in the code below. Your biggest problems were a) you didn't actually load the data from the CSV (you only created a variable with the file path in it), and b) you were attempting to use $ResourceID without ever populating it.
I cleaned things up a bit and it works now, and will throw an error if it can't find the VM. Here are the contents of my test.csv, with one good VM and one that doesn't exist.
VMName,ResourceGroup,Application,SubCat,Environment,AppOwner,Location
test-rm-01,test-rg,Thing,Subs,Dev,Nick,Europe
clambake,mygroup,,,,,
And here's the re-worked script.
# Set Credentials (Fixed this up)
$context = Get-AzureRmContext
if ($context.Account -eq $null) {
Login-AzureRmAccount
}
# Select Azure Subscription
$subscriptionId = (Get-AzureRmSubscription | Out-GridView -Title "Select an Azure Subscription ..." -PassThru).SubscriptionId
#Select specified subscription ID
Select-AzureRmSubscription -SubscriptionId $subscriptionId
$InputCSVFilePath = "C:\Users\Nick\Desktop\test.csv"
################
#
# Here you were missing loading the actual data. Also renamed the items in the loop, nitpicking. :)
#
################
$csvItems = Import-Csv $InputCSVFilePath
################
#Start loop
foreach ($item in $csvItems)
{
################
#
# Change here, you need ResourceGroupName
# Also cleared r because if you don't, it will still have a value if no matching VM is found, which would re-tag the previous item from the loop, instead of throwing an error that the VM was not found
#
################
Clear-Variable r
$r = Get-AzureRmResource -ResourceGroupName $item.ResourceGroup -Name $item.VMName -ErrorAction Continue
################
if ($r -ne $null)
{
if ($r.Tags)
{
# Tag - Application
if ($r.Tags.ContainsKey("Application"))
{
$r.Tags["Application"] = $item.Application
}
else
{
$r.Tags.Add("Application", $item.Application)
}
# Tag - SubCat
if ($r.Tags.ContainsKey("subCat"))
{
$r.Tags["subCat"] = $item.subCat
}
else
{
$r.Tags.Add("subCat", $item.subCat)
}
# Tag - Environment
if ($r.Tags.ContainsKey("Environment"))
{
$r.Tags["Environment"] = $item.Environment
}
else
{
$r.Tags.Add("Environment", $item.Environment)
}
# Tag - AppOwner
if ($r.Tags.ContainsKey("AppOwner"))
{
$r.Tags["AppOwner"] = $item.AppOwner
}
else
{
$r.Tags.Add("AppOwner", $item.AppOwner)
}
# Tag - Location
if ($r.Tags.ContainsKey("Location"))
{
$r.Tags["Location"] = $item.Location
}
else
{
$r.Tags.Add("Location", $item.Location)
}
#Setting the tags on the resource
Set-AzureRmResource -Tag $r.Tags -ResourceId $r.ResourceId -Force
}
else
{
#Setting the tags on a resource which doesn't have tags
Set-AzureRmResource -Tag #{ Application = $Application; subCat = $subCat; Environment = $Environment; AppOwner = $AppOwner; Location = $Location } -ResourceId $r.ResourceId -Force
}
}
else
{
Write-Host "No VM found named $($item.VMName) in ResourceGroup $($item.ResourceGroup)!"
}
}

Check if Azure VM name exists before deploying arm template

I am trying to use Azure powershell to get VM name (Eg: demovm01, where the VM name is demovm and the suffix is 01.
I want to get the output and automatically append a new suffix of 02 if 01 already exists.
Sample script to get vm name:
$getvm = Get-AzVM -Name "$vmname" -ResourceGroupName "eodemofunction" -ErrorVariable notPresent -ErrorAction SilentlyContinue
if ($notPresent) {
Write-Output "VM not found. Creating now"
}
else {
Write-Output "VM exists."
return $true
}
I want to be able to inject this new vm name to an arm deploy to deploy
This should do it. Will increment until no VM is found and use a simple -replace to inject to your json file. Will also return all thr VM values that are already present in Azure
$i=1
$vmname_base = "vmserver"
$VMexists = #()
do {
#invert int value to double digit string
$int = $i.tostring('00')
$getvm = Get-AzVM -Name "$vmname_base$int" -ResourceGroupName "eodemofunction" -ErrorVariable notPresent -ErrorAction SilentlyContinue
if ($notPresent) {
Write-Output "VM not found. Creating now"
Write-Output "VM created name is $vmname_base$int"
#Set condition to end do while loop
VMcreated = "true"
#commands to inject to json here. I always find replace method the easiest
$JSON = Get-Content azuredeploy.parameters.json
$JSON = $JSON -replace ("Servername","$vmname_base$int")
$JSON | Out-File azuredeploy.parameters.json -Force
}
else {
Write-Output "VMexists."
# Add existing VM to array
$VMexists += "$vmname_base$int"
# Increment version, ie. 01 to 02 to 03 etc
$i++
}
} while ($VMcreated -ne "true")
return $VMexists
Your command could be like below, the $newvmname is that you want.
$vmname = "demovm01"
$getvm = Get-AzVM -Name "$vmname" -ResourceGroupName "<group name>" -ErrorVariable notPresent -ErrorAction SilentlyContinue
if ($notPresent) {
Write-Output "VM not found. Creating now"
}
else {
Write-Output "VM exists."
if($getvm.Name -like '*01'){
$newvmname = $vmname.TrimEnd('01')+"02"
}
Write-Output $newvmname
return $true
}

Resources