I'm currently studying for az-104 azure exam and going through some scripting exercises.
I can confirm the script below works, but I don't understand how...
in the last few lines of the script the variable $vm is defined twice. how is this possible?
also when you define a variable is it actually running the commands that are being defined? i didn't realize that was the case, but it definitely seems to be.. can someone please explain?
$resourcegroup = "2019dc_group"
$machinename = "2019dc"
$location = "east us"
$storagetype = "Standard_LRS"
$name = "newdisk"
$size = 20
$datadiskconfig = new-azdiskconfig -SkuName $storagetype -location $location -createoption empty -DiskSizeGB $size
$datadisk01 = new-azdisk -diskname $name -disk $datadiskconfig -ResourceGroupName $resourcegroup
$vm = Get-AzVM -name $machinename -resourcegroupname $resourcegroup
$vm = add-azvmdatadisk -vm $vm -name $name -createoption attach -ManagedDiskId $datadisk01.id -lun 1
update-azvm -vm $vm -resourcegroupname $resourcegroup
Variable types in PowerShell are dynamic. They are automatically "adjusted" to the type of the object they are assigned to.
Yes, the commands are being executed first and then the object is placed in the variable. This is also why you are able to use the vm variable in the command and assign the result back to that variable. When the "add-azvmdatadisk" command is executed, the type of the variable is still an Azure VM. When the assignment takes places, it is an Azure VM Data Disk
You can use Get-Member to get the type, methods, properties, events etc. for any object.
$a = 1 #System.Int32
$a | Get-Member
$a = "1" #System.String
$a | Get-Member
$a = Get-Service | Select-Object -First 1 #System.ServiceProcess.ServiceController
$a | Get-Member
The $VM variable is not defined twice. It's being assigned a value twice. One doesn't need to define variables in Powershell, assignment will automatically create one.
Let's review the code. First off,
$vm = Get-AzVM -name $machinename -resourcegroupname $resourcegroup
After the Get-AzVM, you'll have its output in $vm. Nothing fancy here. Note that before this statement, $vm didn't exist, and trying to work with it would generate an error about using null value.
$vm = add-azvmdatadisk -vm $vm -name $name -createoption attach -ManagedDiskId $datadisk01.id -lun 1
Here $vm is used twice. The first usage is when you pass it as an argument to Add-AzVmDataDisk. The cmdlet returns a PSVirtualMachine object, so after adding the disk, you'd get an updated version of $vm into the variable. This might or might not be important a difference. If the Azure VM objects are lazy evaluated, the old $vm would contain hardware information without the new disk. This often is the case, as it improves performance. By assigning the output of the later cmdlet into $vm, you are sure to have up-to-date version of your VM.
If you have a lab environment, try printing $vm before the last disk addition cmdlet call and after it. See what's different on the outputs.
Related
I'm trying to parallelize a portion of a bigger PWSH script, basically I need to cycle through a list of VMs and execute some commands on them, and to speed up the process, I need this to be executed in parallel:
#Utils.ps1 contains all the functions
.(".\src\utils.ps1")
# List of VMs on which we execute the space reclaim
$VMs = Get-Content .\vmnames.txt
# Check if directory ./Results exists, if not it will be created
CheckDirResults
# Check if dir temp exists, if not it will be created
CheckDirTemp
# Asks for vCenter credentials
GetVServerCred
# Asks for VM credentials
GetVMCred
# Connects to vCenter
ConnectVcenter
# Commands that will be executed on VMs on line 94 (Invoke-VMScript)
$InvokeScript = #'
#! /bin/bash
pwd
'#
foreach ($vm in $VMs) {
$vms = Get-VM -Name $vm
$vms | ForEach-Object -Parallel {
Write-Output "Testing on $_"
Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $InvokeScript -Confirm:$false
}
}
I also tried to simply execute the Invoke directly on $VMs like this:
$VMs | ForEach-Object -Parallel {
Write-Output "Testing on $_"
Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $commands
}
In both cases, the Invoke-VMScript can't either connect to the VMs or can't find the chunk of code to execute on the VMs.
Errors:
"test.ps1" 44L, 934C 36,0-1 81%
---- ---- ----
evl6800756.sys.ntt.eu 443 VSPHERE.LOCAL\Administrator
Testing on euczdrpoc03
Testing on euczdrpoc30
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript You are not currently connected to any servers. Please connect first using a Connect cmdlet.
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript Value cannot be found for the mandatory parameter ScriptText
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript You are not currently connected to any servers. Please connect first using a Connect cmdlet.
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript Value cannot be found for the mandatory parameter ScriptText
The $using: scope modifier inside a -Parallel script-block lets you refer to variables assigned a value outside that script-block. Examples in your question are not complete enough to say for certain, but hopefully this explains at least some of the difficulty.
So for example within the parallel block, try $using:commands instead of your $commands. This bit from reference doc could be more helpfully spelled out, IMHO.
Use the $using: keyword to pass variable references to the running
script.
That's demonstrated more clearly in one of the reference examples, in this blog post, and this answer.
Edit: sounds like the parallel runspaces do not inherit the vCenter connection. The open proposal for a -UseCurrentState switch to transfer the current runspace state to -Parallel loop iterations might help in your situation. Meanwhile could try passing -Server $using:DefaultVIServer to relevant commands inside the parallel block. LucD's comment that PowerCLI is not threadsafe may also be worth researching alongside the note in the reference doc:
The ForEach-Object -Parallel parameter set runs script blocks in parallel on separate process threads. The $using: keyword allows passing variable references from the cmdlet invocation thread to each running script block thread. Since the script blocks run in different threads, the object variables passed by reference must be used safely. Generally it is safe to read from referenced objects that don't change. But if the object state is being modified then you must used thread safe objects, such as .NET System.Collection.Concurrent types
I managed to do it this way:
# Commands that will be executed on VMs on line 94 (Invoke-VMScript)
$commands = #'
#! /bin/bash
hostname
'#
# Loads the VMs names from the file vmnames.txt
$vms = Get-Content ".\vmnames.txt"
# Uses the content of the file vmnames.txt to get the VM objects
$vmnames = Get-VM -Name $vms
# Executes the commands on the VMs
$vmnames | ForEach-Object -Parallel {
# Loads the variables in the parallel session
$commands = $using:commands
$VMCred = $using:VMCred
Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $commands
}
For some reasons, piping directly $vms (so the content of the file to ForEach-Object -Parallel won't pass correctly the value (in this case the VM name), while using the Get-VM -Name $vms will do.
Then, once this is solved the ForEach-Object -Parallel will finally get the correct VM name assigned to $_.
I am trying to write a PowerShell script that will look for shutdown VMs in my Resource Group and deallocate them. The output of the below script does not give me the VM name "clean" when I attempt tp assign the below as a variable. The end result is to execute the Stop-AzureRmVM -ResourceGroupName LAB -Name $VM -force
So for more context, lets say AVGJOE-DC1 is in a stopped state and I run the below line in Azure Powershell it will display
Name
----
AVGJOE-DC1
If I then if I tried to use $VM to call AVGJOE-DC1 in the
Stop-AzureRmVM -ResourceGroupName LAB -Name $VM -force
it fails due to the variable being set to a longer string something like
MicroSoftComputerResource\Resourcegroup[#Name=AVGJOE-DC1].
Hopefully that makes sense.
$VM = Get-AzureRmVM -ResourceGroupName LAB | get-azurermvm -Status | ?{$_.statuses.displaystatus -eq "VM stopped"} | select name
Just like #Theo said in the comment, select name gives you an object with property name. If you want the string value of the name property, you can use Select-Object -ExpandProperty name instead of select name.
I have recently started using Set-StrictMode to get into better scripting habbits (i.e. declaring variables and such) and I have run into a small issue. For most of my scripts I will create a hashtable $Script = #{} and then declare all variables used within the script as sub properties under that because no matter what, when you start the script, all the variables will be clean and if you print out all the variables at the end of your script you know they will be from that specific session.
Historically if I needed to see if a subvariable such as $Script.RunOnce was declared I would just use If ($Script.RunOnce) {} but with strict mode you have to do something along the lines of this If (Test-Path Variable:\Script.WriteOnce) {} except test-path sees "Script.WriteOnce" as its own variable, not a sub variables underneath $Script
Why do I need to do this you may ask? Well I am writing a function that uses .Net Streamwriter and I want to make sure that if the variable "$WriteTee.StreamWriter" exists run the $WriteTee.StreamWriter.Close and $WriteTee.StreamWriter.Flush prior to declaring $Write-Tee again or else when I attempt to open a new streamwriter it will error out and I have to manually close the .net handle on the file before I can continue testing the script.
Long story short
Is there a way to test for $WriteTee.StreamWriter using Test-Path or some other way that doesn't create an error with Set-Strictmode
Super gutted example verison of my script.
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Inquire'
Function Write-Tee {
Begin {
#Variables that are needed only the first time the log function is started.
If ($WriteTee.RunOnce) {
If ($WriteTee.StreamWriter) { Write-Tee -Severity Error -Message "Log Writer Already Open. Attempting to close." -Close}
New-Variable -ErrorAction SilentlyContinue -Force -Name WriteTee -Value #{} -Scope Script
$Script:WriteTee.RunOnce = $True
}
}#End-Begin
Process {}#End-Process
End {
If ($Close -AND $Script:WriteTee) {
Write-Tee -Severity Info -Message "Flushing Log Writer and closing."
$WriteTee.StreamWriter.Flush()
$WriteTee.StreamWriter.Close()
Remove-Variable -ErrorAction SilentlyContinue -Force -Name WriteTee -Scope Script
Remove-Variable -ErrorAction SilentlyContinue -Force -Name WriteTee
}#End-If
}#End-End
}
Write-Tee -Message "Test" -Severity "Warning"
You can use test-path with the variable provider and the name of the variable to find out if a variable has been assigned to, but finding properties of a variable (or items in a hashtable, it's not clear which you are dealing with) requires other tactics:
To find out if a hashtable has an item with a key, you can do something like:
$writeTee.ContainsKey('StreamWriter') #returns $true or $false
To find out if a variable has a particular property, you can use get-member:
$writeTee | Get-Member -name StreamWriter #returns the member or nothing
I have a powershell script to deploy a service bus namespace
This is the command i use to deploy it .
New-AzureRmServiceBusNamespace -ResourceGroup $ResourceGroupName -NamespaceName $ServiceBusNamespace -Location $Location
The above command expects a location parameter to be entered. I want that location value as the Location where my resource group is located . How do i extract the Location value of my resource Group ?
I tried various methods and failed. it shows #{location=west us} , when i tried this
$location = Set-AzureRmResourceGroup -Name $ResourceGroupName -Tag #{} | Select-Object Location
Write-Host $location
Need help . Thanks !
$loc = Get-AzureRMResourceGroup -Name $ResourceGroupName | select-object -expandproperty location
Well, if you want to GET something use appropriate verb.
or like this:
$rg = Get-AzureRMResourceGroup -Name $ResourceGroupName
$rg.location
The below code works with Az powershell module.
$loc= Get-AzResourceGroup -Name $resourceGroupName #Gets the resource group details and stores in $loc variable
$location= $loc.location #Gets the location name from resource group properties and stores in $location variable
Write-Host $location #outputs the resource group location
I am trying to find PowerShell cmdlet which can retrieve information about which storage account Azure VM use.
Example, I want to supply name of VM and I would like to see which storage that VM use. Or it will be also good to query specific storage account and see which VMs use this storage account.
I am trying following cmdlets but I cannot see details about storage account:
Select-AzureSubscription -SubscriptionName "name"
Get-AzureVM -Name "name" -ServiceName "name"
The Get-AzureDisk cmdlet may be useful for you. This is the approach that I'm using.
$disk = Get-AzureDisk | Where-Object { $_.AttachedTo.RoleName -eq "YOURVMNAME" }
$mediaLink = $disk.MediaLink
$storageAccountName = $mediaLink.Host.Split('.')[0]
https://msdn.microsoft.com/en-us/library/azure/dn495125.aspx
I am answering the second part of your question. You want list of VM under a particular Storage Account. Suppose your storage account name is "xyz123" and you want to list all vms under this storage account. Then you just need to run the script given below-
$vms = Get-AzureVM
$output = " "
$tgtStorageaccount = "xyz123"
foreach($vm in $vms)
{
$disk = Get-AzureVM -ServiceName $vm.ServiceName –Name $vm.Name | Get-AzureOSDisk
$mediaLink = $disk.MediaLink
$storageAccountName = $mediaLink.Host.Split('.')[0]
if ($storageAccountName -eq $tgtStorageaccount)
{
$output =$output + "`r`n" + $vm.Name
}
}
$output
Hope This one will help you. Thanks.
Yes. Storageaccount can be find for RM based VM.
For OS:
$output = Get-AzureRmVM -ResourceGroupName "xx-RG-xx-DEv" -Name "xx-xx-vm-DEV"
$storageAccountName = $output.StorageProfile.OsDisk.Vhd.Uri.Split("/")[2].Split(".")[0]
Get-AzureRmStorageAccount -StorageAccountName $storageAccountName -ResourceGroupName "xx-RG-xx-DEv"
Each of a vm's disks is going to be stored in a given blob, with the blob's uri containing the storage account name. For example:
https://mystorageaccount.blob.core.windows.net/vhds/myosdisk.vhd
You'd need to retrieve the various os disk and data disk uri's from your vm's and then parse the uri accordingly. Here's a way to get the storage account for each disk by first grabbing the base uri (mystorageaccount.blob.core.windows.net) and then taking the first string segment (separated by dot characters).
For the OS disk:
Get-AzureVM -ServiceName myservice -Name myvmname |
GetAzureOSDisk |
ForEach-Object { $_.MediaLink.Host.split(".")[0] }
And likewise for data disks:
Get-AzureVM -ServiceName myservice -Name myvmname |
GetAzureDataDisk |
ForEach-Object { $_.MediaLink.Host.split(".")[0] }
You'll get duplicate storage account names if you used the same storage account for more than one disk.