Azure Runbook | Sending attachments from my Azure Storage Account in Transactional Emails - azure

I have an Azure Runbook script set up for transaction emails, the SMTP server details, & the from and to emails redacted for security.
For some reason, PowerShell is not seeing the file with which I want to send as an attachment, can anyone see where I am going wrong?
I have included a screenshot as proof that I have the correct file (I believe they're called 'blobs'?) name, storage account name and container name
Below is the Runbook PowerShell script, along with the error messages upon testing the email.
All the best,
===================RUNBOOK POWERSHELL CODE===========================
$Username ="xxx"
$Password = ConvertTo-SecureString "xxx" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $Username, $Password
$SMTPServer = "xxx"
$EmailFrom = "xxx"
[string[]]$EmailTo = "xxx"
$Subject = "xxx"
# Setting the Azure Storage Context,recommedation is to read sastoken from Runbook assets for security purpose.
$context = New-AzureStorageContext -StorageAccountName "expertadvisers" -SasToken "?sv=2019-12-12&ss=bfqt&srt=c&sp=rwdlacupx&se=2021-01-20T17:01:17Z&st=2021-01-20T09:01:17Z&spr=https&sig=PpozvhXnD6k4knwLLpyJvMF0vpnSZATabreYTzr0s2k%3D"
#Get the blob contents from storage container and store it local destination .
Get-AzureStorageBlob -Container "notificationcontainer" -Blob "test_file.docx" -Context $context | Get-AzureStorageBlobContent -force -Destination .
#Get contents of the file
[string[]] $attachment = "test_file.docx"
$Body = "TEST MESSAGE"
Send-MailMessage -smtpServer $SMTPServer -Attachment $attachment -Credential $credential -Usessl -Port 587 -from $EmailFrom -to $EmailTo -subject $Subject -Body $Body -Priority High -DeliveryNotificationOption OnSuccess -BodyAsHtml
Write-Output ("Email sent succesfully.")
===================ERROR MESSAGE===========================
Get-AzureStorageBlob : The remote server returned an error: (403) Forbidden. HTTP Status Code: 403 - HTTP Error
Message: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly
including the signature.
At line:19 char:1
+ Get-AzureStorageBlob -Container "notificationcontainer" -Blob "test_f ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : CloseError: (:) [Get-AzureStorageBlob], StorageException
+ FullyQualifiedErrorId :
StorageException,Microsoft.WindowsAzure.Commands.Storage.Blob.Cmdlet.GetAzureStorageBlobCommand
Send-MailMessage : Could not find file 'C:\Temp\pyxr0xjf.rej\test_file.docx'.
At line:32 char:1
+ Send-MailMessage -smtpServer $SMTPServer -Attachment $attachment -Cre ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Send-MailMessage], FileNotFoundException
+ FullyQualifiedErrorId : System.IO.FileNotFoundException,Microsoft.PowerShell.Commands.SendMailMessage

Regarding the issue, please refer to the following steps
Create SAS token via protal
Script
$Username =""
$Password = ConvertTo-SecureString "" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $Username, $Password
$Subject = "test"
$To=""
$Body = "This is an automated mail send from Azure Automation relaying mail using Office 365."
$Context = New-AzureStorageContext -StorageAccountName "andyprivate" -SasToken "<the sas token you created in step1>"
$a=Get-AzureStorageBlobContent -Container "output" -Blob "test.txt" -Context $Context
$attachment = $a.Name
Send-MailMessage `
-To $To `
-Subject $Subject `
-Body $Body `
-UseSsl `
-Port 587 `
-SmtpServer 'smtp.office365.com' `
-From $Username `
-Attachments $attachment `
-BodyAsHtml `
-Credential $credential

Related

Connect-AzureAD : parsing_wstrust_response_failed: Parsing WS-Trust response failed

I am getting the following below error when every time i run my script to connect to Azure Ad via Powershell
Connect-AzureAD : One or more errors occurred.: parsing_wstrust_response_failed: Parsing WS-Trust response failed
At C:\Azure-AD\Azure-Connect.ps1:10 char:1
+ Connect-AzureAD -TenantId $TenantId -credential $MyCredential
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : AuthenticationError: (:) [Connect-AzureAD], AadAuthenticationFailedException
+ FullyQualifiedErrorId : Connect-AzureAD,Microsoft.Open.Azure.AD.CommonLibrary.ConnectAzureAD
Below is the script i have created
$TenantId = ""
$SecFile = "C:\Azure-AD\Password.txt"
$SecUser = "C:\Azure-AD\UserName.txt"
$MyCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $SecUser,
(Get-Content $SecFile | ConvertTo-SecureString)
Connect-AzureAD -TenantId $TenantId-credential $MyCredential
I am using the following line to generate to encrypt my password
(Get-Credential).Password | ConvertFrom-SecureString | Out-File "C:\AzureAD\Password.txt"
Any solutions on how I can fix the errors and connect to Azure Ad via Powershell
I have tested in my environment.
Please use the below script :
$tenantID = ""
$secfile = "C:\Azure-AD\Password.txt";
$secuser = "C:\Azure-AD\UserName.txt";
$username = Get-Content $secuser;
$password = Get-Content $secfile | ConvertTo-SecureString -AsPlainText -Force;
$MyCredential = New-Object Management.Automation.PSCredential ($username, $password);
Connect-AzureAD -TenantId $tenantID -credential $MyCredential

get-quarantinemessages in Azure Automation

If i try the script below in Azure Automation, I get the error Get-QuarantineMessages is not recognized ...
This cmdlet dont work in Azure Automation?
$MyCredential = "O365Credential"
$subject = "Mail send from Azure Automation using Office 365"
$userid='xistoso#xistoso.ca'
# Get the PowerShell credential and prints its properties
$Cred = Get-AutomationPSCredential -Name $MyCredential
if ($Cred -eq $null)
{
$Body = Write-Output "Credential entered: $MyCredential does not exist in the automation service. Please create one `n"
}
else
{
$ExchangeOnlineSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Cred -Authentication Basic -AllowRedirection -Name $ConnectionName
Import-Module (Import-PSSession -Session $ExchangeOnlineSession -AllowClobber -DisableNameChecking) -Global
$today = get-date
$yesterday = (get-date).AddHours(-1)
$Body = Get-QuarantineMessage -Direction Inbound -StartReceivedDate $yesterday -EndReceivedDate $today
<#$CredUsername = $Cred.UserName
$CredPassword = $Cred.GetNetworkCredential().Password
Write-Output "-------------------------------------------------------------------------"
Write-Output "Credential Properties: "
Write-Output "Username: $CredUsername"
Write-Output "Password: *************** `n"
Write-Output "-------------------------------------------------------------------------"
# Write-Output "Password: $CredPassword `n" 3#>
}
Send-MailMessage -To 'xisto#xisto.com' -Subject $subject -Body $Body -UseSsl -Port 587 -SmtpServer 'smtp.office365.com' -From $userid -BodyAsHtml -Credential $Cred
Write-Output "Mail is now send `n"
Write-Output "-------------------------------------------------------------------------"
Remove-PSSession $ExchangeOnlineSession

Can you download an Azure Blob using Virtual Machine Identity Access Token through PowerShell without using Access Keys

I'm setting up a VM to do some bootstrapping on creation.
Part of this is to download a blob from an azure storage account to the VM.
These are all in the same subscription, resource group, etc.
I can do it this way fine:
function Get-BlobUsingVMIdentity
{
param(
[Parameter(Mandatory = $true)] $containerName,
[Parameter(Mandatory = $true)] $blobName,
[Parameter(Mandatory = $true)] $outputFolder
)
write-host "Defining package information"
mkdir $outputFolder -force
write-host "Getting Instance meta data"
$instanceInfo = Invoke-WebRequest -UseBasicParsing -Uri 'http://169.254.169.254/metadata/instance/?api-version=2018-02-01' `
-Headers #{Metadata="true"} `
| select -expand content `
| convertfrom-json `
| select -expand compute
$storageAccountName = "$($instanceInfo.resourceGroupName.replace('-rg',''))sa" # This is custom since we know our naming schema
$resourceGroupName = $($instanceInfo.resourceGroupName)
$subscriptionId = $($instanceInfo.subscriptionId)
write-host "Got storageAccountName [$storageAccountName], resourceGroupName [$resourceGroupName], subscriptionId [$subscriptionId]"
write-host "Getting VM Instance Access Token"
$response = Invoke-WebRequest -UseBasicParsing -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F' `
-Headers #{Metadata = "true" }
$content = $response.Content | ConvertFrom-Json
$access_token = $content.access_token
write-host "Getting SAS Token From Storage Account"
$params = #{canonicalizedResource = "/blob/$($storageAccountName)/$($containerName)"; signedResource = "c"; signedPermission = "rcw"; signedProtocol = "https"; signedExpiry = "2031-09-23T00:00:00Z" }
$jsonParams = $params | ConvertTo-Json
$sasResponse = Invoke-WebRequest -UseBasicParsing -Uri "https://management.azure.com/subscriptions/$($subscriptionId)/resourceGroups/$($resourceGroupName)/providers/Microsoft.Storage/storageAccounts/$($storageAccountName)/listServiceSas/?api-version=2017-06-01" `
-Method POST `
-Body $jsonParams `
-Headers #{Authorization="Bearer $access_token"}
$sasContent = $sasResponse.Content | ConvertFrom-Json
$sasCred = $sasContent.serviceSasToken
write-host "Manually download blob"
$params = #{signedResource = "c"; signedPermission = "rcw"; signedProtocol = "https"; signedExpiry = "2031-09-23T00:00:00Z" }
$jsonParams = $params | ConvertTo-Json
$sasResponse = Invoke-WebRequest -UseBasicParsing -Uri "https://$($storageAccountName).blob.core.windows.net/$($containerName)/$($blobName)?api-version=2017-06-01" `
-Method POST `
-Body $jsonParams `
-Headers #{Authorization="Bearer $access_token"}
$sasContent = $sasResponse.Content | ConvertFrom-Json
$sasCred = $sasContent.serviceSasToken
write-host "Setting up storage context"
$ctx = New-AzStorageContext -StorageAccountName $storageAccountName -SasToken $sasCred
write-host "Downloading package"
Get-AzStorageBlobContent `
-Blob $blobName `
-Container $containerName `
-Destination $outputFolder `
-Context $ctx `
-Force
}
This works fine, except I have to grant full/write access to the identity in order for it to use the access key.
Is there a similar approach that would allow read only access to the blob?
My goals are:
1. No credentials stored anywhere
2. Download blob to VM from azure storage
3. No statically defined variables (ex: subscriptionid)
4. Read only access to the blob/storage account.
Appreciate any help!
According to my understanding, you want to use Azure VM MSI to access Azure storage. If so, please refer to the following steps:
Enable a system-assigned managed identity on a VM
Connect-AzAccount
$vm = Get-AzVM -ResourceGroupName myResourceGroup -Name myVM
Update-AzVM -ResourceGroupName myResourceGroup -VM $vm -AssignIdentity:$SystemAssigned
Grant your VM access to an Azure Storage container
Connect-AzAccount
$spID = (Get-AzVM -ResourceGroupName myRG -Name myVM).identity.principalid
New-AzRoleAssignment -ObjectId $spID -RoleDefinitionName "Storage Blob Data Reader" -Scope "/subscriptions/<mySubscriptionID>/resourceGroups/<myResourceGroup>/providers/Microsoft.Storage/storageAccounts/<myStorageAcct>/blobServices/default/containers/<container-name>"
access blob
# get AD access token
$response = Invoke-WebRequest -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F' `
-Headers #{Metadata="true"}
$content =$response.Content | ConvertFrom-Json
$access_token = $content.access_token
# call Azure blob rest api
$url="https://<myaccount>.blob.core.windows.net/<mycontainer>/<myblob>"
$RequestHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$RequestHeader.Add("Authorization", "Bearer $access_token")
$RequestHeader.Add("x-ms-version", "2019-02-02")
$result = Invoke-WebRequest -Uri $url -Headers $RequestHeader
$result.content
Update
According to my test, when we get token to access Azure blob, we need to change resouce as https://storage.azure.com/
# get AD access token
$response = Invoke-WebRequest -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://storage.azure.com/' `
-Headers #{Metadata="true"}
$content =$response.Content | ConvertFrom-Json
$access_token = $content.access_token
# call Azure blob rest api
$url="https://<myaccount>.blob.core.windows.net/<mycontainer>/<myblob>"
$RequestHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$RequestHeader.Add("Authorization", "Bearer $access_token")
$RequestHeader.Add("x-ms-version", "2019-02-02")
$result = Invoke-WebRequest -Uri $url -Headers $RequestHeader
$result.content
Update
Found a much easier way to do it!
The key was to use:
Connect-AzAccount -identity
This auto logs in a the identity and allows you to interact through AZ module instead of trying to hack API calls together.
You only need [Storage Blob Data Reader] assigned to the container to pull this off.
Working example looks like this:
function Get-BlobUsingVMIdentity
{
param(
[Parameter(Mandatory = $true)] $containerName,
[Parameter(Mandatory = $true)] $blobName,
[Parameter(Mandatory = $true)] $outputFolder
)
write-host "Get instance info"
$instanceInfo = Invoke-WebRequest -UseBasicParsing -Uri 'http://169.254.169.254/metadata/instance/?api-version=2018-02-01' `
-Headers #{Metadata="true"} `
| select -expand content `
| convertfrom-json `
| select -expand compute
$storageAccountName = "$($instanceInfo.resourceGroupName.replace('-rg',''))sa" # This is custom since we know our naming schema
write-host "Clear existing identies to keep cache fresh"
Clear-AzContext -force
write-host "Authenticate using identity"
$account = Connect-AzAccount -identity
if(-not $account.Context.Subscription.Id)
{
write-error "Failed to authenticate with identity. Ensure VM has identity enabled and is assigned the correct IAM roles"
return
}
write-host "Get storage context"
$ctx = New-AZStorageContext -StorageAccountName $storageAccountName
write-host "Getting blob"
Get-AzStorageBlobContent `
-Blob $blobName `
-Container $containerName `
-Destination $outputFolder `
-Context $ctx `
-Force
}
Get-BlobUsingVMIdentity `
-containerName "deploy" `
-blobName "deploy.zip" `
-outputFolder "c:\deploy\"

Azure Runbook Commands

So I have a runbook which automates the shutdown and start-up of my Azure VM during the weekends. This then sends a transactional email confirming that the VPS is shut down/started up.
I have set up my parameters as illustrated. Is there a reason as to why it correctly states the name of my virtual machine (highlighted) in the subject line but in the body of the email (highlighted), it comes up with a completely different name.
Logic would dictate that $VM.NAME would be the name of the VPS and not some random command line, so why is this? It's displayed correctly in the subject line but not the email body.
param (
[Parameter(Mandatory=$false)]
[String] $VMName ="ITAMTRADINGVPS",
[Parameter(Mandatory=$false)]
[String] $ResourceGroupName
)
$connectionName = "AzureRunAsConnection"
try
{
# Get the connection "AzureRunAsConnection "
$servicePrincipalConnection=Get-AutomationConnection -Name $connectionName
"Logging in to Azure..."
Add-AzureRmAccount `
-ServicePrincipal `
-TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
}
catch {
if (!$servicePrincipalConnection)
{
$ErrorMessage = "Connection $connectionName not found."
throw $ErrorMessage
} else{
Write-Error -Message $_.Exception
throw $_.Exception
}
}
# If there is a specific resource group, then get all VMs in the resource group,
# otherwise get all VMs in the subscription.
if ($ResourceGroupName -And $VMName)
{
$VMs = Get-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $VMName
}
elseif ($ResourceGroupName)
{
$VMs = Get-AzureRmVM -ResourceGroupName $ResourceGroupName
}
else
{
$VMs = Get-AzureRmVM
}
$vms
# Start each of the VMs
# Stop each of the VMs
foreach ($VM in $VMs)
{
$StopRtn = $VM | Stop-AzureRmVM -Force -ErrorAction Continue
$StopRtn
Write-Output " this is $StopRtn "
if ($StopRtn.IsSuccessStatusCode -eq 'True')
{
# The VM stopped, so send notice
Write-Output ($VM.Name + " has been stopped")
$Username ="xxx"
$Password = ConvertTo-SecureString "xxx" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $Username, $Password
$SMTPServer = "xxx"
$EmailFrom = "xxxx
[string[]]$EmailTo = "xxx"
$Subject = $VM.NAME + " notification of scheduled deallocation"
$Body = "We'd like to let you know that your Virtual Machine $VM.NAME has successfully deallocated.
<br>This could either be due to maintenance or a scheduled shutdown. If you were expecting this notification, please disregard this email.
<br><br>If you need any further assistance, please contact the system administrator on xxx<br><br>Yours Sincerely<br><br>The Technical Design Team<br>xxx<br><br>"
Send-MailMessage -smtpServer $SMTPServer -Credential $credential -Usessl -Port 587 -from $EmailFrom -to $EmailTo -subject $Subject -Body $Body -BodyAsHtml
Write-Output "Email sent succesfully."
}
else
{
# The VM failed to stop, so send notice
Write-Output ($VM.Name + " failed to stop")
}
}
Illustration
The variable reference (e.g. $VM.Name) into the email body which by default will just return the object type where the output in PowerShell is a special pipeline activity which will render the content either as a listing or as a table. In order to include content in an email, we would have to reference the properties
[string]$EmailBody = (“VMNAME IS = [{0}]” -f $VM.Name)
which is similar to string.format in C#
Refer this SO

Export SQL Azure db to blob - Start-AzureSqlDatabaseExport : Cannot convert AzureStorageContainer to AzureStorageContainer

I am using this code found on stack , and all connections are correct.
Import-Module Azure
Import-Module Azure.Storage
Get-AzureRmSubscription –SubscriptionName “Production” | Select-AzureRmSubscription
# Username for Azure SQL Database server
$ServerLogin = "username"
# Password for Azure SQL Database server
$serverPassword = ConvertTo-SecureString "abcd" -AsPlainText -Force
# Establish credentials for Azure SQL Database Server
$ServerCredential = new-object System.Management.Automation.PSCredential($ServerLogin, $serverPassword)
# Create connection context for Azure SQL Database server
$SqlContext = New-AzureSqlDatabaseServerContext -FullyQualifiedServerName “myspecialsqlserver.database.windows.net” -Credential $ServerCredential
$StorageContext = New-AzureStorageContext -StorageAccountName 'prodwad' -StorageAccountKey 'xxxxx'
$Container = Get-AzureStorageContainer -Name 'automateddbbackups' -Context $StorageContext
$exportRequest = Start-AzureSqlDatabaseExport -SqlConnectionContext $SqlContext -StorageContainer $Container -DatabaseName 'Users' -BlobName 'autobackupotest.bacpac' -Verbose -Debug
I am getting this error. I have spent hours on this.
Start-AzureSqlDatabaseExport : Cannot bind parameter 'StorageContainer'. Cannot convert the
"Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageContainer" value of type
"Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageContainer" to type
"Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageContainer".
At line:31 char:99
+ ... SqlConnectionContext $SqlContext -StorageContainer $Container -Databa ...
+ ~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Start-AzureSqlDatabaseExport], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.WindowsAzure.Commands.SqlDatabase.Database.Cmdlet.StartAzureSqlDatabaseExport
I am using AzureRM 3.8.0
According to your description and codes, If you want to start a new sqldatabase export.I suggest you could try below codes. It will work well.
$subscriptionId = "YOUR AZURE SUBSCRIPTION ID"
Login-AzureRmAccount
Set-AzureRmContext -SubscriptionId $subscriptionId
# Database to export
$DatabaseName = "DATABASE-NAME"
$ResourceGroupName = "RESOURCE-GROUP-NAME"
$ServerName = "SERVER-NAME"
$serverAdmin = "ADMIN-NAME"
$serverPassword = "ADMIN-PASSWORD"
$securePassword = ConvertTo-SecureString -String $serverPassword -AsPlainText -Force
$creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $serverAdmin, $securePassword
# Generate a unique filename for the BACPAC
$bacpacFilename = $DatabaseName + (Get-Date).ToString("yyyyMMddHHmm") + ".bacpac"
# Storage account info for the BACPAC
$BaseStorageUri = "https://STORAGE-NAME.blob.core.windows.net/BLOB-CONTAINER-NAME/"
$BacpacUri = $BaseStorageUri + $bacpacFilename
$StorageKeytype = "StorageAccessKey"
$StorageKey = "YOUR STORAGE KEY"
$exportRequest = New-AzureRmSqlDatabaseExport -ResourceGroupName $ResourceGroupName -ServerName $ServerName `
-DatabaseName $DatabaseName -StorageKeytype $StorageKeytype -StorageKey $StorageKey -StorageUri $BacpacUri `
-AdministratorLogin $creds.UserName -AdministratorLoginPassword $creds.Password
$exportRequest
# Check status of the export
Get-AzureRmSqlDatabaseImportExportStatus -OperationStatusLink $exportRequest.OperationStatusLink
Then you could use Get-AzureRmSqlDatabaseImportExportStatus to see the details information, like below:
I got the same problem after updating some powershell packages which I do not remember exactly. After the update my scripts started to fail.
My solution is :
Install the latest AzureRM from nuget via powershell
Use the other overload of Start-AzureSqlDatabaseExport which utilizes the parameters
-StorageContainerName and -StorageContext rather than -StorageContainer
It looks like if you pass the parameters to the function it will create the container object internally without crashing.

Resources