Utilizing Azure Custom Script Extensions to maintain a custom PowerShell module across multiple servers - azure

I'm trying to automate some tasks at work and someone gave me an idea that we could utilize Azure Custom Script Extensions to maintain a custom PowerShell module across multiple servers.
The script is located on our Azure DevOps, but I assume I could create a Pipeline that moves it to storage account after a pull request is completed.
However, my question is - how does Custom Script Extension work in the first place and would it be suitable for my needs, which are having an up-to-date PowerShell module available on multiple servers?

Were you able to find the answer on your question?
As I know, after the Custom Script Extension is installed the script will be executed once.
Below code snippet shows how you could deploy Custom Script Extension on Multiple VMs:
$CustomerScriptExtension = "yourCSEName"
$AllVMs = Get-AzureRMVM #get all VMs
$CntOfVMs = 0;
foreach($vm in $AllVMs)
{
try
{
$CntOfVMs = $CntOfVMs + 1
Write-Host "VMCount:" $CntOfVMs
#Get the VM details of that instance
$vmDetails = Get-AzureRmVM -ResourceGroupName $vm.ResourceGroupName -Name vm.Name
#Check if already an extension exists of type Custome Script Extension
$cseExtension = $vmDetails.Extensions | where { $_.Publisher -eq
"Microsoft.Compute" -and $_.VirtualMachineExtensionType -eq "CustomScriptExtension" }
# If Extension already exists remove the extension
if ($cseExtension)
{
#Remove the extension
Write-Host "Removing existing extension on VirtualMachine:" $vm.Name
Remove-AzureRmVMCustomScriptExtension -ResourceGroupName
$vm.ResourceGroupName -VMName $vm.Name -Name $cseExtension.Name
Write-Host "Completed Removal of existing extension on VirtualMachine:"
$vm.Name
}
try
{
Write-Host "Installing extenstion on VirtualMachine :" $vm.Name
Set-AzureRmVMCustomScriptExtension -ResourceGroupName
$vm.ResourceGroupName -VMName $vm.Name -Location $vm.Location -FileUri
PathToyourPowerShellCodeToUpdateVMGoesThere.ps1 -Run "InstalluptodatePowerShell.ps1" -name
$CustomerScriptExtension
Write-Host "completed Extension installation on VirtualMachine :"
$vm.Name
}
catch
{
Write-Host "Error installing extension on VirtualMachine :" $vm.Name
}
}
catch
{
Write-Host "Error in setting extension on VM:" $vm.Name
}
}

Related

Any way to get rid of an obsolete parameter in Azure PowerShell script (AzureRM to Az changing)

As you may know, MSFT is getting rid of AzureRM cmdlets in favor of Az.
There are a lot of issues regarding this since the proposed native aliases "Enable-AzureRmAlias" seems to stop being updated.
I have a script based on AzureRM in one repo, that triggers by Azure DevOps release pipeline step function (Azure PowerShell based),
that has the following piece of code:
$var = (Get-AzureKeyVaultSecret -VaultName $vaultName-Name $Key).SecretValueText
"Enable-AzureRmAlias" command activated as well..., that converts the code like this:
$var = (Get-AzKeyVaultSecret -VaultName $vaultName-Name $Key).SecretValueText
The problem is, that ".SecretValueText" was deprecated a while ago. Instead of it, a new parameter has been added to the Get-AzKeyVaultSecret cmdlet - "-AsPlainText"
so... theoretically the final construction has to be like this:
$var = Get-AzKeyVaultSecret -VaultName $vaultName-Name $Key -AsPlainText
Challenges!
I can't upgrade the original script in the repo to Az due to the necessity of back-compatibility.
The only way to solve it - is to create some kind of alias in Azure PowerShell inline script (that triggers the main script in the repo)
I stuck with this ".SecretValueText"
My original idea to put the following into the inline script doesn't seem to be working:
function Get-AzKeyVaultSecretNew {
Param(
$vaultName,
$Key
)
$var = Get-AzKeyVaultSecret -VaultName $vaultName -Name $Key -AsPlainText
return $var
}
Set-Alias -Name Get-AzKeyVaultSecret -Value Get-AzKeyVaultSecretNew
Any ideas on how to accomplish this?
This should theoretically help your situation. You can run this code at the beginning of the PowerShell session that will be calling your scripts. You will need to make sure any necessary modules are loaded so that the secret object types are loaded.
$Script = { Get-AzKeyVaultSecret -VaultName $this.VaultName -Name $this.Name -AsPlainText }
Update-TypeData -TypeName 'Microsoft.Azure.Commands.KeyVault.Models.PSKeyVaultSecretIdentityItem' -MemberName 'SecretValueText' -MemberType ScriptProperty -Value $Script
The idea is to add the SecretValueText property back to the Microsoft.Azure.Commands.KeyVault.Models.PSKeyVaultSecretIdentityItem objects.
You can try using below workaround to replace below piece of code:
(Get-AzureKeyVaultSecret -VaultName $vaultName-Name $Key).SecretValueText
with Get-AzKeyVaultSecret -VaultName $vaultName -Name $Key -AsPlainText via using RegEx Find & Replace task. Check below steps:
1, Add task RegEx Find & Replace to replace the orginal code with the converted code. See below:
FindRegex: '\(Get-AzureKeyVaultSecret -VaultName \$vaultName -Name \$Key\)\.SecretValueText'
ReplaceRegex: 'Get-AzKeyVaultSecret -VaultName $vaultName -Name $Key -AsPlainText'
2, -AsPlainText parameter is only available in the latest az 5.3.0 version. Since the version installed in cloud agent is 4.7.0. You need to install the az 5.3.0 version before executing your script. See below. Use a powershell task to install az 5.3.0 version.
New-Item -Path "C:\Modules" -Name "az_5.3.0" -ItemType "directory"
Save-Module -Name AZ -RequiredVersion 5.3.0 -Path "C:\Modules\az_5.3.0"
3, Then you can invoke your script in the azure powershell task directly.

Assistance needed with powershell script

I am trying the execute the powershell script found at :
https://github.com/amanbedi18/Azure-KeyVault-Automation/tree/master/UploadSecrets
https://github.com/amanbedi18/Azure-KeyVault-Automation
This script is used to automate upload of secrets to azure key-vault.
I have created the below json file and named it /user/kv/AzureSecretsMetaData.json
[
{
"key": "test1",
"value": "1"
},
{
"key": "test2",
"value": "2"
}
]
I then copied contents of setKeyVaultSecrets.ps1 to /user/kv/setKeyVaultSecrets.ps1
I have tried the below commands that were mentioned in the readme and powershell script files and get the below errors, could someone please help me execute this script correctly and point out what I am doing wrong here.
./setkeyVaultSecret.ps1 -KeyVaultName 'avkv01'
setkeyVaultSecret.ps1: Parameter set cannot be resolved using the specified named parameters. One or more parameters issued cannot be used together or an insufficient number of parameters were provided.
./setkeyVaultSecret.ps1 -KeyVaultName 'avkv01' -KVSecretMetadataFilePath '/home/aditya/kv/AzureSecretsMetaData.json'
setkeyVaultSecret.ps1: Parameter set cannot be resolved using the specified named parameters. One or more parameters issued cannot be used together or an insufficient number of parameters were provided.
Any help is greatly appreciated. Thank You
If these are scripts you found, they are very poorly written and I would advise not using them. I would suggest installing the az powershell module and using those scripts as a reference in writing your own script. You can install the az powershell module with powershellget:
Install-module az -force -allowclobber
The way the script that you are trying to call is written very strangely. It defines two different parameter sets, but each of the two parameters are mandatory for BOTH parameter sets?! That doesn't make a lot of sense. If you must run them, you will probably need to modify them a bit. They don't really seem finished.. Why not modify the script and get rid of the parameter sets altogether? Also, the example in the script indicates that the "$KVSecretMetadataFilePath" parameter is not mandatory, since it is not being called, but is defined as being mandatory when defining your parameter. You need to make clear if this parameter is required or not. If it is required Make it look more like this:
<#
.PREREQUISITE
1. An Azure key vault and its name as parameter.
2. Json template should be properly populated with valid json schema in sampleSecretValues.json in KeyVaultjson directory.
.PARAMETER vaultName
The name of the key vault.
.EXAMPLE
. setKeyVaultSecret.ps1 -KeyVaultName 'somekeyvault'
#>
# provision keys and secrets to a key vault
Param(
[Parameter(Mandatory=$true)]
[String]
$KeyVaultName,
[Parameter(Mandatory=$true)]
[String]
$KVSecretMetadataFilePath
)
Install-Module -Name AzureADPreview -ErrorAction SilentlyContinue -Force
Import-Module Azure -ErrorAction SilentlyContinue
Import-Module AzureRM.Resources
Set-StrictMode -Version 3
$json = Get-Content $KVSecretMetadataFilePath | Out-String | ConvertFrom-Json
$json | ForEach {
$secretToSearch = Get-AzureKeyVaultSecret -VaultName $KeyVaultName -Name $_.key -ErrorAction SilentlyContinue
if($secretToSearch -ne $null)
{
echo "The secret $_.key already exists !"
}
Else
{
$NewSecret = Set-AzureKeyVaultSecret -VaultName $KeyVaultName -Name $_.key -SecretValue (ConvertTo-SecureString $_.value -AsPlainText -Force ) -Verbose
Write-Host
Write-Host "Source Vault Resource Id: "$(Get-AzureRmKeyVault -VaultName $KeyVaultName).ResourceId
}
}
If it is not required, remove the line "[Parameter(Mandatory=$true)]" above the $KVSecretMetadataFilePath declaration.

Azure - setting tags on VMs using Powershell

I'm using the following Powershell code to set tags on a bunch of Azure VMs but it doesn't seem to be working. It does not give me an error message, so it appears to run correctly, but it does not set the tags as expected. Any help would be great, perhaps it may be something easy I'm overlooking.
$group = Get-AzResourceGroup -Name my-rg
$resource = Get-AzResource -ResourceName myserver -ResourceGroupName my-rg
Set-AzResource -ResourceId $resource.ResourceId -Tag $group.Tags -Force
I'm only doing a few VMs which I'll probably put on a loop once this works. There are no tags on most of these VMs which may be overwritten.

Using Set-AzStorageBlobContent to upload only new content without prompts

I'm enumerating a local folder and uploading to Azure storage. I want to only upload new content to my Azure storage. If I use Set-AzStorageBlobContent with -Force, it'll overwrite everything. If I use it without -Force, it'll prompt on items that already exist. I can use Get-AzStorageBlob to check if the item already exists, but it prints red errors if the item does not exist. I can't find a combination of these items that gracefully uploads only new content without printing any errors or prompting. Am I using the wrong approach?
FINAL EDIT: adding working solution based on suggestions from Ivan Yang. Now only new files are uploaded, without any error messages. The key was to use -ErrorAction Stop to convert the error message into an exception, and then catch the exception.
# In my code this is part of a Test-Blob function that returns $blobFound
$blobFound = $false
try
{
$blobInfo = Get-AzStorageBlob `
-Container $containerName `
-Context $storageContext `
-Blob $blobPath `
-ErrorAction Stop
$blobFound = ($null -ne $blobInfo)
}
catch [Microsoft.WindowsAzure.Commands.Storage.Common.ResourceNotFoundException]
{
# Eat the error that'd otherwise be printed
}
# Note in my code this is actually a call to my Test-Blob function
if ($false -eq $blobFound)
{
Set-AzStorageBlobContent `
-Container $containerName `
-Context $storageContext `
-File $sourcePath `
-Blob $blobPath `
-Force # -Force is unnecessary but just being paranoid to avoid prompts
}
I see you have mentioned trying Get-AzStorageBlob, why not use it continually?
The trick here is that you can use try-catch-finally, which can properly handle the error if the blob does not exist in azure.
The sample code works at my side for uploading a single file, and you can modify it to upload multi-files:
$account_name ="xxx"
$account_key ="xxx"
$context = New-AzStorageContext -StorageAccountName $account_name -StorageAccountKey $account_key
#use this flag to determine if a blob exists or not in azure. And assume it exists at first.
$is_exist = $true
try
{
Get-AzStorageBlob -Container test3 -Blob a.txt -Context $context -ErrorAction Stop
}
catch [Microsoft.WindowsAzure.Commands.Storage.Common.ResourceNotFoundException]
{
#if the blob does not exist in azure, do the following
$is_exist = $false
Write-Output "the blob DOES NOT exists."
}
finally
{
#only execute the code when the blob does not exist in azure blob storage.
if(!$is_exist)
{
Set-AzStorageBlobContent -Container test3 -File "d:\myfolder\a.txt" -Blob a.txt -Context $context
Write-Output "uploaded!"
}
}
Not a PowerShell solution but I would suggest that you take a look at AzCopy. It's like RoboCopy but for Azure storage. A command line tool which allows you to synch, copy, move and more. It's free, works on macOS, Linux and Windows. And also, it is fast!
I use AzCopy from PowerShell scripts and it makes lie a lot easier (I'm managing millions of files and the stability and speed of AzCopy really helps)
This command is not smart enough to detect which files are new. You need to keep in the folder just the files you want to upload.
Simply use Set-AzStorageBlobContent -Force all the time.
The alternative is to check for existing file, download the file content, compare the files, and upload if different. The amount of processing/IO will only increase this way.

IIS: how to undeploy/delete/remove a webapp from command line?

Suppose there's a webapp deployed on local IIS server. When I need to remove/undeploy it, I can go to IIS Manager, right-click on the app, and then select "Delete application and content" - et voila. But, I need to do the same from the command line - how? It can be assumed that the name of the application is known.
Maybe this can be done via MSDeploy somehow?
If you just want to remove the application from the Web Site in IIS without physically deleting the files (like msdeploy does) or if you don't have the WebDeploy-extension installed, you can use the following command:
C:\Windows\System32\inetsrv\appcmd.exe delete app "Default Web Site/MyAppName"
This is what did it:
"C:\Program Files\IIS\Microsoft Web Deploy\msdeploy" -verb:delete -dest:apphostconfig="Default Web Site/<webapp_name>"
I know the question says "command line", but you can use PowerShell and the IIS Administration Cmdlets to do this task. I provide all of the functions and explain the process of how to automate this on my blog. Also, you can easily swap out the IIS Administration Cmdlet calls with calls to msdeploy, appcmd, IIsVdir.vbs, etc.
For your specific question, this PowerShell code should do the trick:
$block = {
Import-Module WebAdministration
$website = "YourWebsiteName"
$applicationName = "PathUnderWebsite\ToYourApplication"
$fullPath = Join-Path $website $applicationName
Write-Host "Checking if we need to remove '$fullPath'..."
if (Get-WebApplication -Site "$website" -Name "$applicationName")
{
Write-Host "Removing '$fullPath'..."
Remove-WebApplication -Site "$website" -Name "$applicationName"
}
Write-Host "Deleting the directory '$fullPath'..."
Remove-Item -Path "IIS:\Sites\$fullPath" -Recurse -Force
}
$session = New-PSSession -ComputerName "Your.WebServer.HostName"
Invoke-Command -Session $session -ScriptBlock $block
Remove-PSSession -Session $session
iisweb /delete WebSite [/s Computer [/u [Domain ]User /p Password ]]

Resources