How to pass multiple parameters in a function in Powershell? - azure

I am trying to run a Powershell script for a runbook in an automation account in Azure. I am using a function to pass multiple parameters. The runbook is for restoring disks of VM. However from the code structure that I am using it doesn't seem like I can pass in more than 1 Resource Name. How do I restructure this code or how do I frame my function or anyone of the lines in the script in order to pass more than one Resource Name (VM names in this case) ?
Currently I can pass only one Resource Name in the function but I need to pass two VM names.
function DiskRestore($ResourceName, $storageAccount)
{
try
{
.....
.....
.....
$resources = Get-AzureRmResource -ResourceGroupName"samplerg" -ResourceName $ResourceName
.....
.....
.....
$restorejob = Restore-AzureRmRecoveryServicesBackupItem -RecoveryPoint $rp[0] -StorageAccountName $storageAccount -StorageAccountResourceGroupName "samplerg"
.....
.....
.....
}
}
DiskRestore -ResourceName "samplevm01" -storageAccount "samplesa01"
So in the above function how can I pass one more VM name apart from samplevm01. How can I pass samplevm02 ? The storage account remains the same. Thanks in advance.

As commented by Vesper, change the parameter definition to receive a string array and loop through that array with foreach{..}.
Personally I would extend this a little by putting the parameters in a Param() block to create an 'advanced function'. By doing so, the function automatically gains extra common PowerShell parameters like ErrorAction, Verbose etc.
Also, I would rename the function so it complies with the Verb-Noun naming convention for PowerShell functions.
Something like this:
function Restore-Disk {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$ResourceName,
[string]$ResourceGroupName = 'samplerg', # add a default name for this parameter
[string]$StorageAccount = 'samplesa01', # add a default name for this parameter
[Parameter(Mandatory = $true)]
$RecoveryPoint
)
foreach ($resource in $ResourceName) {
try {
# .....
# This will only print to the console is the -Verbose parameter is used on the function
Write-Verbose "Retrieve resource '$resource' in group '$ResourceGroupName'"
$resources = Get-AzureRmResource -ResourceGroupName $ResourceGroupName -ResourceName $resource
# .....
Write-Verbose "Restoring data and configuration for group '$ResourceGroupName'"
$restorejob = Restore-AzureRmRecoveryServicesBackupItem -RecoveryPoint $RecoveryPoint -StorageAccountName $StorageAccount -StorageAccountResourceGroupName $ResourceGroupName
# .....
}
catch {}
}
}
# Because the parameters ResourceGroupName and StorageAccount have default values, you do not have to specify them
# in the call to the function unless the are different than the default values.
Restore-Disk -ResourceName "samplevm01","samplevm02" -RecoveryPoint $rp[0] -Verbose

If you want to run the powershell in powershell runbook, you can just pass the vm names in a string type and separated by a comma, like "vm1,vm2,vm3". Then in your runbook code, split the string to array, then in the foreach loop, operates with each vm.
A simple test in powershell runbook:
param([string]$ResourceName,[string]$StorageAccount)
#parse the string to an array
$vms = $ResourceName.split(',')
#implement your logic here
foreach($r in $vms)
{
#output the vm name
$r
}
#output the storage account
$StorageAccount
Pass the parameters and run:
For your code, you can follow the above steps, your code in runbook should look like below:
param([string]$ResourceName,[string]$StorageAccount)
$vms = $ResourceName.split(',')
foreach ($resource in $vms) {
try {
#.....
$resources = Get-AzureRmResource -ResourceGroupName "samplerg" -ResourceName $resource
$restorejob = Restore-AzureRmRecoveryServicesBackupItem -RecoveryPoint $rp[0] -StorageAccountName $StorageAccount -StorageAccountResourceGroupName "samplerg"
#....
}
catch {}
}

Related

Why are write-host statements are not appearing when calling a script with an azure commandlet in it?

I have a very simplistic 2 scripts and I'm trying to call the powershell script from another powershell run script
run script (run.ps1)
.\NewRG.ps1 -rgName "singleVM12" -location "Canada Central" -tags #{dept="Marketing"}
called script (newRG.ps1)
[CmdletBinding()]
param (
[string]$rgName = "Test1-rg",
[string]$location = "Canada Central",
[Parameter(Mandatory)]
[hashtable]$tags)
$newRG = New-AzResourceGroup -name $rgName -location $location -tags #{dept="marketing"}
write-output "test"
I would expect that I should get test in the console but I get the properties of the Resource group
ResourceGroupName : singleVM12
Location : canadacentral
ProvisioningState : Succeeded
The issue is I have more complex scripts with multiple write-host entries that I want shown but none of it appears when I run the "run.ps1" file, it works fine if I just call the called script by itself. I tried using write-output and same thing happens. I noticed that hello world works, so I'm guessing something about the Azure commandlets are maybe causing this. Any way around this?
I am using Write-Output to print the values in prompt. I have followed the same way you did.
Follow the workaround:
testout.ps1
# I am getting resource information
[CmdletBinding()]
param (
[string]$rgName = "test")
#$newRG = New-AzResourceGroup -name $rgName -location $location -tags #{dept="marketing"}
$getResource = Get-AzResource -ResourceGroupName $rgName
write-output "azure resoure get successfully- " $rgName
$getResource = Get-AzResource -ResourceGroupName $rgName
write-output "test2- " $rgName
$getResource = Get-AzResource -ResourceGroupName $rgName
write-output "test3 - " $rgName
$getResource = Get-AzResource
write-output "test4- " $rgName
# you can use return to send the the required data to promt as well. But you can use end of your script otherwise it will skip after the return statement.
return $getResource.ResourceGroupName
test2.ps1
Calling testout.ps1 in test2.ps1 script.
# Connect Azure Account using specific Subscription Id if you are using more subscription in single account
Connect-AzAccount -SubscriptionId '<Your subscription Id>'
# calling test.ps1 script
.\testout.ps1 -rgName "<Your Resourcegroup Name>"
Result

Public Containers in Storage Accounts

First post. My Powershell knowledge isnt great. I am trying to list out all containers that have the "PublicAccess" attribute set to On. I am trying to use the script provided by MS.
$rgName = "<Resource Group name>"
$accountName = "<Storage Account Name>"
$storageAccount = Get-AzStorageAccount -ResourceGroupName $rgName -Name $accountName
$ctx = $storageAccount.Context
Get-AzStorageContainer -Context $ctx | Select Name, PublicAccess
However I need to do this on a large amount of storage accounts. In the past I have used "foreach($item in $list)" to pass things into a small script. But never for multiple lists. Can anyone help?
Based off your extended requirements in the comments, this should work. (I've not tested it and not handled any potential errors related to permissions or anything else)
# Create a collection for the items found.
$StorageAccounts = [System.Collections.Generic.List[System.Object]] #{}
# Loop through the available Azure contexts.
foreach ($Context in (Get-AzContext -ListAvailable)) {
# Set each subscription in turn, voiding the output.
[System.Void](Set-AzContext -Context $Context)
# Create an object with the container name, public access values and the name of the storage account/subscription.
$StorageAccountInfo = Get-AZStorageAccount | Get-AzStorageContainer | Select-Object Name, PublicAccess, #{l = "StorageAccountName"; e = { $_.Context.StorageAccountName } }, #{l = "Subscription"; e = { $Context.Subscription.Name } }
# If there is data found, add it to the collection.
if ($null -ne $StorageAccountInfo) {
$StorageAccounts.AddRange($StorageAccountInfo)
}
}
# Export the collected information to Csv.
$StorageAccounts | Export-Csv -Path .\myStorageAccounts.csv -NoClobber -Encoding utf8

ARM Template deployment returing a value that I dont expect

I am working on writing some Powershell that I am going to use in Azure DevOPS pipelines. This is to create an AppServiceplan. I have included the code below. I have used this same basic template to deploy a few other things and I have not run into this problem before.
This piece of the code $appSP = Get-AzAppServicePlan -Name $appServicePlanName -ResourceGroupName $rgname does not return a value, which is expected because this is the first time running it, yet its triggering this piece of code Write-verbose "AppService Plan '$appServicePlanName' already exists."
So it seems that even though the Get-AzAppServicePlan appears to be blank, maybe its returning some sort of object that I cant see?
Any ideas?
function Assert-AppServicePlan {
<#
.SYNOPSIS
This function will ensure that specified AppServicePlan exists.
.DESCRIPTION
Function will check if specified Azure AppServicePlan exists, and if not - it will be created.
Will return AppServicePLan object or, in case of error, $null.
.PARAMETER appServicePlanName
App Service Plan to be checked or created.
.PARAMETER rgName
Resource Group.
.PARAMETER SKU
SKU for App Service Plan. Default is P2V2.
.EXAMPLE
Assert-AppServicePlan -appServicePlansName "BARF" -rgName "MyResourceGroup" -SKU "F1" -verbose
#>
param(
[Parameter(Mandatory = $true)][ValidateNotNullorEmpty()][string]$appServicePlanName,
[Parameter(Mandatory = $true)][ValidateNotNullorEmpty()][string]$rgName,
[Parameter(Mandatory = $true)][ValidateNotNullorEmpty()][string]$SKU
);
$appSP = Get-AzAppServicePlan -Name $appServicePlanName -ResourceGroupName $rgname
if ($notPresent)
{
Write-Verbose "App Service Plan '$appServicePlanName' doesn't exist. Creating it."
try
{
$appSP = New-AzResourceGroupDeployment -ResourceGroupName `
$rgname -TemplateFile .\templates\appserviceplan.json `
-TemplateParameterFile .\templates\appserviceplan.parameters.json `
-ErrorAction Stop
}
catch
{
Write-verbose "Error while creating App Service Plan."
Write-Error $_
}
}
else
{
Write-verbose "AppService Plan '$appServicePlanName' already exists."
}
return $appSP
}
The Get-AzAppServicePlan cmdlet returns the App Service Plan object if found in the specified resource group, else nothing. So, it would be sufficient to check on this result in your if condition.
Another case where the result might mislead returning null is when you query under an incorrect PS context or a different Subscription (locally). However, since you intend to run this as part of Azure DevOps pipelines, the Service connection used by the task would ensure it.
Just another suggestion, you can use ErrorVariable
Get-AzAppServicePlan -Name $appServicePlanName -ResourceGroupName $rgname -ErrorAction Silentlycontinue -ErrorVariable notPresent
if ($notPresent)
{
#create something
}
else {
#exits
}

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.

Passing multiple Parameters in single Azure Storage Script for various environments

I have a powershell script that creates the storage and blob account for a given subscription that works fine . Subscription Name, resource group keeps changing for different environments like DEV,UAT,PROD
STRUCTURE OF MY TEMPLATE / CODE :
param(
[string] $subscriptionName ="ABC",
[string] $resourceGroupName = "XYZ",
[string] $resourceGroupLocation ="westus",
[string] $templateFilePath = "template.json",
[string] $parametersFilePath = "parameters.json"
)
Function RegisterRP {
Param(
[string]$ResourceProviderNamespace
)
Write-Host "Registering resource provider '$ResourceProviderNamespace'";
Register-AzureRmResourceProvider -ProviderNamespace $ResourceProviderNamespace;
}
$ErrorActionPreference = "Stop"
$confirmExecution = Read-Host -Prompt "Hit Enter to continue."
if($confirmExecution -ne '') {
Write-Host "Script was stopped by user." -ForegroundColor Yellow
exit
}
# sign in
Write-Host "Logging in...";
Login-AzureRmAccount;
# select subscription
Write-Host "Selecting subscription '$subscriptionName'";
Select-AzureRmSubscription -SubscriptionName $subscriptionName;
# Register RPs
$resourceProviders = #("microsoft.storage");
if($resourceProviders.length) {
Write-Host "Registering resource providers"
foreach($resourceProvider in $resourceProviders) {
RegisterRP($resourceProvider);
}
}
#Create or check for existing resource group
$resourceGroup = Get-AzureRmResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue
if(!$resourceGroup)
{
Write-Host "Resource group '$resourceGroupName' does not exist. To create a new resource group, please enter a location.";
if(!$resourceGroupLocation) {
$resourceGroupLocation = Read-Host "resourceGroupLocation";
}
Write-Host "Creating resource group '$resourceGroupName' in location '$resourceGroupLocation'";
New-AzureRmResourceGroup -Name $resourceGroupName -Location $resourceGroupLocation
}
else{
Write-Host "Using existing resource group '$resourceGroupName'";
}
# Start the deployment
Write-Host "Starting deployment...";
if(Test-Path $parametersFilePath) {
New-AzureRmResourceGroupDeployment -ResourceGroupName $resourceGroupName -Name $deploymentName -TemplateFile $templateFilePath -TemplateParameterFile $parametersFilePath -storageAccounts_name $storageAccountName
} else {
New-AzureRmResourceGroupDeployment -ResourceGroupName $resourceGroupName -Name $deploymentName -TemplateFile $templateFilePath; -storageAccounts_name $storageAccountName
}
Approach 1 :
Created multiple powershell scripts for each denvironment
Created 1 Menu Based powershell script that calls the other script and executes like : Select 1 for Dev , 2 for UAt , 3 for PROD , this approach works but is not effective .
Approach 2 :
I would like to combine all scripts and just have one script for all environments and based on select should allow me to create the storage accounts. Only Subscription and resource group change rest all structure of the powershell remains same .
I tried using GET function commandlets and it selects but still throws the error
[string] $subscriptionName = Get-AzureSubscription,
[string] $resourceGroupName = Get-AzureRmLocation,
If i try to use it using an array based approach like passing the values as below im unable to understand how do i pass these array based values to the code and get it to work .
$environment=#('DEV','TEST','QA','PROD')
$resourcegroupname = #('test','test1','test2','test3')
$subscriptionName = #('devsub1','devsub2','test3','prod4')
I'm trying to call the functions using :
$environment[0]
$subscriptionName[0]
It returns the value as below if i execute it seperately but how do i pass these values to my script to create storage account ?
DEV
devsub1
Requesting expert help if anyone has come across such scenarios earlier and if you can help in changing the above code and provide a tested code that would be of great help.
APPROACH 3:
$subscription = #(Get-AzureRmSubscription)
$resourcegroup = #(Get-AzureRmResourceGroup)
$Environment = #('DEV','TEST','QA','PROD')
$resourceGroupName = $resourcegroup | Out-GridView -PassThru -Title 'Pick the environment'
$subscriptionName = $subscription | Out-GridView -PassThru -Title 'Pick the subscription'
Write-Host "Subscription:" $subscriptionName
Write-Host "ResourceGroup:" $resourcegroup
OUTPUT :
If you look at resource group it fails to give the selection option for resource group .
Subscription: < it returns the subscription name >
ResourceGroup: Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels.PSResourceGroup Microsoft.Azure.Commands.ResourceManager.Cmd
lets.SdkModels.PSResourceGroup Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels.PSResourceGroup Microsoft.Azure.Commands.Res
ourceManager.Cmdlets.SdkModels.PSResourceGroup
What you are proposing is an interesting approach. I would likely an input parameter that defines which environment the work will be done in, and then have a conditional block that sets the dynamic variables for that environment. There would be some duplication of initialization code for each environment, but the main code block would still be unified.

Resources