I have written a PowerShell script to print VM names with unmanaged disks however it's giving me an error. Appreciate any help on this -
$location=Read-Host -Prompt 'Input location for VMs'
$azuresubscription=Read-Host -Prompt 'Input Subscription Id'
$rmvms=Get-AzurermVM
# Add info about VM's from the Resource Manager to the array
foreach ($vm in $rmvms)
{
# Get status (does not seem to be a property of $vm, so need to call Get-AzurevmVM for each rmVM)
$vmstatus = Get-AzurermVM -Name $vm.Name -ResourceGroupName $vm.ResourceGroupName -Status | where Location -like $location
# Add values to the array:
$vmarray += New-Object PSObject -Property #{`
# Subscription=$Subscription.SubscriptionName; `
Subscription=$azuresubscription.SubscriptionName; `
AzureMode="Resource_Manager"; `
Name=$vm.Name; PowerState=(get-culture).TextInfo.ToTitleCase(($vmstatus.statuses)[1].code.split("/")[1]); `
Size=$vm.HardwareProfile.VirtualMachineSize}
}
foreach ($vm in $vmarray)
{
$vmdiskstatus = (Get-AzurermVM -Name $vm.Name -ResourceGroupName $vm.ResourceGroupName).StorageProfile.OsDisk.ManagedDisk
if (!$vmdiskstatus) {Write-Host $vm.Name}
}
Error Message:
($vmarray is resulting in Null array) -
Cannot index into a null array.
Expected Output - $vmarray should have one VM as there is a running
instance in eastus (that's what I am using as value for $location)
As requested by Victor Silva to add my comment as answer, here goes:
You need to define the $vmarray as array before entering the loop to add objects to it.
Then, after that loop, the foreach ($vm in $vmarray) does have an array to index into, even if empty:
$location=Read-Host -Prompt 'Input location for VMs'
$azuresubscription=Read-Host -Prompt 'Input Subscription Id'
$rmvms=Get-AzurermVM
###################################################
# create an array variable to collect the result(s)
###################################################
$vmarray = #()
# Add info about VM's from the Resource Manager to the array
foreach ($vm in $rmvms)
{
# rest of your code
}
Related
I am trying to create a powershell that will grab the varibles from an excel sheet and then add them to the powersehll command.
in the excel sheet i have 3 columns i am interested in the data from (Name , resourcegroup, location)
And then for each line with this i want it to parse into into the varible field for the powershell
I have created the powershell to do what i need but it would be better if it could loop through and pull this as I am just running the command again with different machine info manually added from the excel.
With #Theo Help
I am working with this version of the script now
Import-Csv -Path 'c:\scripts\vmtest.csv' | ForEach-Object {
# combine the VMName with suffix '-Snapshot'
$snapshotName = $vm.name + "-Snapshot"
$SnapshotStorage = "Azure-Snapshots"
$vm = Get-AzVM -ResourceGroupName $_.ResourceGroup -Name $_.Name
# using splatting for better readability
$configParams = #{
SourceUri = $vm.StorageProfile.OsDisk.ManagedDisk.Id
Location = $_.location
CreateOption = 'copy'
}
$snapshot = New-AzSnapshotConfig #configParams
New-AzSnapshot -Snapshot $snapshot -SnapshotName $snapshotname -ResourceGroupName $snapshotstorage
}
If as you have commented, you now have the data stored in a CSV file that might look something like this:
Name,ResourceGroup,Location
PRD-ITM001,SJAVIRTUALMACHINES,uksouth
TST-GRSSQL001,SJAVIRTUALMACHINES,uksouth
it has become very simple to import that data and loop through the records like below:
Import-Csv -Path 'c:\scripts\vmtest.csv' | ForEach-Object {
# combine the VMName with suffix '-Snapshot'
$snapshotName = '{0}-Snapshot' -f $_.Name
$SnapshotStorage = "Azure-Snapshots"
$vm = Get-AzVM -ResourceGroupName $_.ResourceGroup -Name $_.Name
# using splatting for better readability
$configParams = #{
SourceUri = $vm.StorageProfile.OsDisk.ManagedDisk.Id
Location = $_.Location
CreateOption = 'copy'
}
$snapshot = New-AzSnapshotConfig #configParams
New-AzSnapshot -Snapshot $snapshot -SnapshotName $snapshotName -ResourceGroupName $_.ResourceGroup
}
Note that the above code assumes your CSV uses the (default) comma as delimiter character. If in your case this is some other character, append parameter -Delimiter followed by the character the csv uses.
Inside a ForEach-Object {..} loop, the $_ automatic variable references the current record from the csv
I used Splatting for better readability of the code. This helps on cmdlets that take a long list of parameters and eliminates the use of the backtick.
Based on the above shared requirement, we understood that you want to pull the values of ResourceGroupName, VMName from the excel sheet & also you want to use those values in the script further.
Using PSExcel Module, We have written the below PowerShell Script which will pull the ResourceGroupName, VMName from excel & it will run Get-AzVM Cmdlet.
Before running the below PowerShell script , run the below cmdlet Save-Azcontext cmdlet it will saves the current authentication information for use in other PowerShell sessions.
Connect-AzAccount
Save-AzContext -Path C:\test.json
Here is the PowerShell script:
$currentDir = "C:\Program Files\WindowsPowerShell\Modules" ##pass the path of the PSexcel Module
Import-Module $currentDir"\PSExcel"
Import-AzContext -Path C:\test.json ##passing the azcontext file path which was saved earlier
$ExcelFile = "Give here the path of the current folder where scripts are stored"
$objExcel = New-Excel -Path $ExcelFile
$WorkBook = $objExcel|Get-Workbook
ForEach($Worksheet in #($Workbook.Worksheets)){
$totalNoOfRecords = $Worksheet.Dimension.Rows
$totalNoOfItems = $totalNoOfRecords-1
# Declare the starting positions first row and column names
$rowNo,$colResourceGroupName = 1,1
$rowNo,$colVMName = 1,2
if ($totalNoOfRecords -gt 1){
#Loop to get values from excel file
for($i=1;$i -le ($totalNoOfRecords-1);$i++){
$ResourceGroupName=$Worksheet.Cells.Item($rowNo+$i,$colResourceGroupName).Value
$VMName=$Worksheet.Cells.Item($rowNo+$i,$colVMName).Value
Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName |select -Property Name,ResourceGroupName,Location
}
}
}
Here is the sample output for reference:
For more information ,you refer this blog post on How to Read excel file using PSExcel Module in PowerShell.
I am reviewing a script that is supposed to delete a vm along with all of the resources attributed to the vm
Write-Host -NoNewline -ForegroundColor Green "Please enter the VM name you would like to remove:"
$VMName = Read-Host
$vm = Get-AzVm -Name $VMName
if ($vm) {
$RGName=$vm.ResourceGroupName
Write-Host -ForegroundColor Cyan 'Resource Group Name is identified as-' $RGName
#boot diagnostics container auto generated in storage account. Auto delete this storageURI property
$diagSa = [regex]::match($vm.DiagnosticsProfile.bootDiagnostics.storageUri, '^http[s]?://(.+?)\.').groups[1].value
Write-Host -ForegroundColor Cyan 'Marking Disks for deletion...'
$tags = #{"VMName"=$VMName; "Delete Ready"="Yes"}
$osDiskName = $vm.StorageProfile.OSDisk.Name
$datadisks = $vm.StorageProfile.DataDisks
$ResourceID = (Get-Azdisk -Name $osDiskName).id
New-AzTag -ResourceId $ResourceID -Tag $tags | Out-Null
if ($vm.StorageProfile.DataDisks.Count -gt 0) {
foreach ($datadisks in $vm.StorageProfile.DataDisks){
$datadiskname=$datadisks.name
$ResourceID = (Get-Azdisk -Name $datadiskname).id
New-AzTag -ResourceId $ResourceID -Tag $tags | Out-Null
}
}
if ($vm.Name.Length -gt 9){
$i = 9
}
else
{
$i = $vm.Name.Length - 1
}
$azResourceParams = #{
'ResourceName' = $VMName
'ResourceType' = 'Microsoft.Compute/virtualMachines'
'ResourceGroupName' = $RGName
}
$vmResource = Get-AzResource #azResourceParams
$vmId = $vmResource.Properties.VmId
$diagContainerName = ('bootdiagnostics-{0}-{1}' -f $vm.Name.ToLower().Substring(0, $i), $vmId)
$diagSaRg = (Get-AzStorageAccount | where { $_.StorageAccountName -eq $diagSa }).ResourceGroupName
$saParams = #{
'ResourceGroupName' = $diagSaRg
'Name' = $diagSa
}
Write-Host -ForegroundColor Cyan 'Removing Boot Diagnostic disk..'
if ($diagSa){
Get-AzStorageAccount #saParams | Get-AzStorageContainer | where {$_.Name-eq $diagContainerName} | Remove-AzStorageContainer -Force
}
else {
Write-Host -ForegroundColor Green "No Boot Diagnostics Disk found attached to the VM!"
}
Write-Host -ForegroundColor Cyan 'Removing Virtual Machine-' $VMName 'in Resource Group-'$RGName '...'
$null = $vm | Remove-AzVM -Force
Write-Host -ForegroundColor Cyan 'Removing Network Interface Cards, Public IP Address(s) used by the VM...'
foreach($nicUri in $vm.NetworkProfile.NetworkInterfaces.Id) {
$nic = Get-AzNetworkInterface -ResourceGroupName $vm.ResourceGroupName -Name $nicUri.Split('/')[-1]
Remove-AzNetworkInterface -Name $nic.Name -ResourceGroupName $vm.ResourceGroupName -Force
foreach($ipConfig in $nic.IpConfigurations) {
if($ipConfig.PublicIpAddress -ne $null){
Remove-AzPublicIpAddress -ResourceGroupName $vm.ResourceGroupName -Name $ipConfig.PublicIpAddress.Id.Split('/')[-1] -Force
}
}
}
Write-Host -ForegroundColor Cyan 'Removing OS disk and Data Disk(s) used by the VM..'
Get-AzResource -tag $tags | where{$_.resourcegroupname -eq $RGName}| Remove-AzResource -force | Out-Null
Write-Host -ForegroundColor Green 'Azure Virtual Machine-' $VMName 'and all the resources associated with the VM were removed sucessfully...'
}
else{
Write-Host -ForegroundColor Red "The VM name entered doesn't exist in your connected Azure Tenant! Kindly check the name entered and restart the script with correct VM name..."
}
I had a question: what does this block of code exactly do:
$diagSa = [regex]::match($vm.DiagnosticsProfile.bootDiagnostics.storageUri, '^http[s]?://(.+?)\.').groups[1].value
I know it matches the storage uri, but how? And why is this needed? I am not sure what the .groups[1].value is referring to either
$diagSa =
[regex]::match($vm.DiagnosticsProfile.bootDiagnostics.storageUri,
'^http[s]?://(.+?).').groups[1].value
I know it matches the storage uri, but how?
You are using the [regex] type accelerator & match method () in the above expression.
The Match() method is a way to instruct PowerShell to attempt to match a string inside of another string. The Match() method has two parameters; the string you'd like to match on and the regular expression you'd like to test against.
Whenever a match is found and a regex group is used; (), the [regex] type accelerator has a Captures property. This Captures property then has a property called Groups. This is a collection that contains lots of attributes of what was matched. The second element in that collection contains the actual value that was matched.
what the .groups[1].value is referring to either
groups[1].values returns the storage account name where the boot diagnostics container resides.
And why is this needed?
When creating an Azure VM, you always have the option of creating a boot diagnostics container. This is useful to troubleshooting VM boot issues but doesn’t get removed when a VM is deleted. Let’s remedy that.
To remove the boot diagnostics container, you first need to figure out the name of the storage account the container resides on. To find that storage account, you’ll have to do some parsing of the storageUri property that’s exists in the DiagnosticsProfile object on the VM.
for more information about [regex]::match().group[1].value expression refer the below blog :
https://mcpmag.com/articles/2015/09/30/regex-groups-with-powershell.aspx
trying to add tags to VMs via CSV data, right now I have the below code:
if ($context.Account -eq $null) {
# Login-AzureAccount
Connect-AzAccount
}
# Select Azure Subscription
$subscriptionId = (Get-AzSubscription | Out-GridView -Title "Select an Azure Subscription ..." -PassThru).SubscriptionId
#Select specified subscription ID
Select-AzSubscription -SubscriptionId $subscriptionId
$InputCSVFilePath = "test.csv"
$csvItems = Import-Csv $InputCSVFilePath
################
foreach ($item in $csvItems){
Clear-Variable r
#$r = Get-AzResource -ResourceGroupName $item.ResourceGroup -Name $item.VM -ErrorAction Continue
$r = Get-AzResource -Name $item.VM
################
if ($r -ne $null){
if ($r.Tags){
# Tag - Client DL
if ($r.Tags.ContainsKey("Client_DL")){
$r.Tags["Client_DL"] = $item.ClientDL
}else{
$r.Tags.Add("Client_DL", $item.ClientDL)
}
# Tag - Priority
if ($r.Tags.ContainsKey("Priority")){
$r.Tags["Priority"] = $item.Priority
}else{
$r.Tags.Add("Priority", $item.Priority)
}
}
}else{
Write-Host "No VM found named $($item.VMName)!"
}
}
I verified that my code does indeed go through the functions but for some reason the tags are not being set on my VM's. I ran the commands manually in powershell and I was able to set a tag by doing:
$r = Get-AzResource -Name TestVM
$r.Tags.Add("Client_DL", "TEST-DL")
Am I missing something? i'm running a Set-PSDebug -Trace 2 when running my code and it seems to check out just fine, but the tags aren't getting set/written.
So you're adding the tags in memory but you're not calling any of the Az cmdlets to set the tags back on the resource.
You can see in the example in the docs here they return the VM with Get-AzResource, append their new tag to the existing tags and then use Set-AzResource to write the newly added tag back.
Just be careful of that
When updating tags through PowerShell, tags are updated as a whole. If you are adding one tag to a resource that already has tags, you will need to include all the tags that you want to be placed on the resource
Alternatively you could use Update-AzTag which has an -Operation parameter and lets you choose whether you want to merge, replace or delete the existing tags.
https://learn.microsoft.com/en-us/powershell/module/az.resources/update-aztag?view=azps-6.0.0
Ultimately you'd need to set back your $r.Tags value to your resource as the last operation within your if statement.
We have a large number of Azure Subscriptions which currently run into the hundreds.
I'm looking to generate a report (ideally using Azure Powershell or Azure CLI) to recursively extract a list of all tags assigned to every single resource within every resource group, for between 40-50 of the subscriptions.
Currently, I can list all tags assigned at Resource Group level, but I simply can't find a way to list the tags assigned to the individual resources within each Resource Group. The list of subscriptions and resource groups on which I'd like to extract this report, are saved in a CSV file which includes two columns displaying the Subscription name and Resource Group respectively.
Any tips on how to achieve the above would be fantastic and most appreciated.
Not detailed code but the idea here.
1.You should write a loop, in the loop, change the subscription each time by using this cmdlet:
Set-AzContext -Subscription $subscription_name.
2.Then get all the resource group in the specified subscription by using this cmdlet:
$resource_groups = Get-AzResourceGroup
3.Then write a nested loop(loop for each resource group), in this nested loop, use this cmdlet to get all azure resources within a resource group:
foreach($rg in $resource_groups){
$azure_resources = Get-AzResource -ResourceGroupName $rg.ResourceGroupName
}
4.Write another nested loop in step 3, this loop is used to go though all the azure resources within the specified resource group. Then use the code below to fetch tags for each azure resource within the resource group:
foreach($r in $azure_resources){
#the following code can get all the tags for one resource
$r.tags
}
Based on Ivan Yang's logic. I have built the PowerShell Script;
#---------DECLARE VARIABLES------------------------------------#
$bnsSubscription = Get-AzSubscription
$day = Get-Date -Format " ddMMMyyyy"
$tagPath = "C:\mytempfolder\"+"$day-Tag-Details.csv"
$tagFolderPath = "C:\mytempfolder\"
#---------DECLARE VARIABLES------------------------------------#
function Get-ResourceTag {
foreach ($subs in $bnsSubscription) {
Select-AzSubscription -SubscriptionName $subs.Name | Out-Null
Write-Host 'The selected Subscription is' ($subs).Name
New-Item -ItemType file -Path "$tagFolderPath\$($subs.Name).csv" -Force
$resource_groups = Get-AzResourceGroup
$resource_groups_details = Get-AzResourceGroup | Sort-Location ResourceGroupName | Format-Table -GroupBy Location ResourceGroupName,ProvisioningState,Tags
Write-Host 'The selected Resource Group is' ($resource_groups).Name 'and the tag information as follows'
#$resource_groups_details
$resource_groups | Select-Object ResourceGroupName,Tags | Export-CSV -Path "$tagFolderPath\$($subs.Name).csv" -Append
$OutputFile = #()
foreach($rg in $resource_groups){
$azure_resources = Get-AzResource -ResourceGroupName $rg.ResourceGroupName
$TestTags = $Resource.Tags.GetEnumerator()
foreach($r in $azure_resources){
Write-Host 'The selected resource is' ($r).Name 'and the information as follows'
$RGHT = New-Object "System.Collections.Generic.List[System.Object]"
$RGHT.Add("RGName",$r.ResourceGroupName)
$RGHT.Add("ResourceName",$r.name)
$RGHT.Add("Location",$r.Location)
$RGHT.Add("Id",$r.ResourceId)
$RGHT.Add("ResourceType",$r.ResourceType)
$RGHT.Add("ResourceTags",$r.Tags)
$OutputFile += New-Object psobject -Property $RGHT
$OutputFile | Export-Csv -Path "C:\mytempfolder\test22.csv" -append -NoClobber -NoTypeInformation -Encoding UTF8 -Force
}
}
}
}
#---------CALL FUNCTION------------------------------------#
Get-ResourceTag
I have a set of commands I can use in azure powershell. The commands create a resource group, app service, etc. I want to bundle them up so that I can just type one command into a terminal and run all of the deployment in one go.
# Ask user for work item id
$workItemId = Read-Host -Prompt "Enter the Work Item ID"
# Set Variables
$appdirectory="C:\Users\Charles\Desktop\Timesheet App\Discover\Client\build"
$webappname="discoverTest$workItemId"
$location="West Europe"
# Create a resource group.
New-AzResourceGroup -Name discoverTest$workItemId -Location $location
# Create an App Service plan in `Free` tier.
New-AzAppServicePlan -Name $webappname -Location $location `
-ResourceGroupName discoverTest$workItemId -Tier Free
# Create a web app.
New-AzWebApp -Name $webappname -Location $location -AppServicePlan $webappname `
-ResourceGroupName discoverTest$workItemId
# Get publishing profile for the web app
$xml = [xml](Get-AzWebAppPublishingProfile -Name $webappname `
-ResourceGroupName discoverTest$workItemId `
-OutputFile null)
# Extract connection information from publishing profile
$username = $xml.SelectNodes("//publishProfile[#publishMethod=`"FTP`"]/#userName").value
$password = $xml.SelectNodes("//publishProfile[#publishMethod=`"FTP`"]/#userPWD").value
$url = $xml.SelectNodes("//publishProfile[#publishMethod=`"FTP`"]/#publishUrl").value
# Upload files recursively
Set-Location $appdirectory
$webclient = New-Object -TypeName System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($username,$password)
$files = Get-ChildItem -Path $appdirectory -Recurse #Removed IsContainer condition
foreach ($file in $files)
{
$relativepath = (Resolve-Path -Path $file.FullName -Relative).Replace(".\", "").Replace('\', '/')
$uri = New-Object System.Uri("$url/$relativepath")
if($file.PSIsContainer)
{
$uri.AbsolutePath + "is Directory"
$ftprequest = [System.Net.FtpWebRequest]::Create($uri);
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::MakeDirectory
$ftprequest.UseBinary = $true
$ftprequest.Credentials = New-Object System.Net.NetworkCredential($username,$password)
$response = $ftprequest.GetResponse();
$response.StatusDescription
continue
}
"Uploading to " + $uri.AbsoluteUri + " from "+ $file.FullName
$webclient.UploadFile($uri, $file.FullName)
}
$webclient.Dispose()
$workItemId = Read-Host -Prompt "Enter the Work Item ID"
Remove-AzResourceGroup -Name "discoverTest$workItemId" -Force
# print variable
Write-Host $variable
I want to be able to run a single command and have the full deployment process executed.
There are two ways to realize your needs, as below.
Extract all parameters you used in these PowerShell command lines as the arguments for a PowerShell Script <your-script-name>.ps1 which includes all same commands as yours. Please refer to the existing SO thread How to handle command-line arguments in PowerShell to know how to do. Then, you just need to run <your-script-name>.ps1 with these arguments in a terminal which had pre-installed Azure PowerShell Module.
Follow the blog Four ways to package a non-GUI PowerShell script as an executable file to make an executable file with the current set of commands.
Normally, I think the first way is better and be recommended.