How to delete the ADFPipeline which is having the references Forcefully - azure

I'm actually some automation for my ADF. As a part of that, I'm trying to delete all the ADF V2 pipelines. The problem is my pipelines having many references with different pipelines itself.
$ADFPipeline = Get-AzDataFactoryV2Pipeline -DataFactoryName $(datafactory-name) -ResourceGroupName $(rg)
$ADFPipeline | ForEach-Object { Remove-AzDataFactoryV2Pipeline -ResourceGroupName $(rg) -DataFactoryName $(datafactory-name) -Name $_.name -Force }
And most of the time I get the error like
The document cannot be deleted since it is referenced by "blabla"
I understand the error that it saying some references and cannot be deleted. However, when I tried the same deletion in the azure portal, irrespective of the reference I can able to delete. So I want to find a way that whether it possible to tell that Powershell even though it's having a reference delete it forcefully
Any other inputs much appreciated!

I run into the same issue, found out that it's rather complicated to build the whole dependency graph out of the pipeline's Activities property.
As a working solution (powershell):
function Remove-Pipelines {
param (
[Parameter(Mandatory=$true)]
[AllowEmptyCollection()]
[AllowNull()]
[System.Collections.ArrayList]$pipelines
)
if($pipelines.Count -gt 0) {
[System.Collections.ArrayList]$plsToProcess = New-Object System.Collections.ArrayList($null)
foreach ($pipeline in $pipelines) {
try {
$removeAzDFCommand = "Remove-AzDataFactoryV2Pipeline -dataFactoryName '$DataFactoryName' -resourceGroupName '$ResourceGroupName' -Name '$($pipeline.Name)' -Force -ErrorAction Stop"
Write-Host $removeAzDFCommand
Invoke-Expression $removeAzDFCommand
}
catch {
if ($_ -match '.*The document cannot be deleted since it is referenced by.*') {
Write-Host $_
$plsToProcess.Add($pipeline)
} else {
throw $_
}
}
}
Remove-Pipelines $plsToProcess
}
}
Here is the complete solution for clearing the whole DF: "trigger","pipeline","dataflow","dataset","linkedService"
Param(
[Parameter(Mandatory=$true)][string] $ResourceGroupName,
[Parameter(Mandatory=$true)][string] $DataFactoryName
)
$artfTypes = "trigger","pipeline","dataflow","dataset","linkedService"
function Remove-Artifacts {
param (
[Parameter(Mandatory=$true)][AllowEmptyCollection()][AllowNull()][System.Collections.ArrayList]$artifacts,
[Parameter(Mandatory=$true)][string]$artfType
)
if($artifacts.Count -gt 0) {
[System.Collections.ArrayList]$artToProcess = New-Object System.Collections.ArrayList($null)
foreach ($artifact in $artifacts) {
try {
$removeAzDFCommand = "Remove-AzDataFactoryV2$($artfType) -dataFactoryName '$DataFactoryName' -resourceGroupName '$ResourceGroupName' -Name '$($artifact.Name)' -Force -ErrorAction Stop"
Write-Host $removeAzDFCommand
Invoke-Expression $removeAzDFCommand
}
catch {
if ($_ -match '.*The document cannot be deleted since it is referenced by.*') {
Write-Host $_
$artToProcess.Add($artifact)
} else {
throw $_
}
}
}
Remove-Artifacts $artToProcess $artfType
}
}
foreach ($artfType in $artfTypes) {
$getAzDFCommand = "Get-AzDataFactoryV2$($artfType) -dataFactoryName '$DataFactoryName' -resourceGroupName '$ResourceGroupName'"
Write-Output $getAzDFCommand
$artifacts = Invoke-Expression $getAzDFCommand
Write-Output $artifacts.Name
Remove-Artifacts $artifacts $artfType
}
The same approach can be adapted for "Set-AzDataFactoryV2Pipeline" command as well.
It worth to mention that along with dependencies tracking, Remove/Set artifact's sequence should be right (because of cross artifacts' dependencies).
For Set - "linkedService","dataset","dataflow","pipeline","trigger"
For Remove - "trigger","pipeline","dataflow","dataset","linkedService"

Hello and thank you for the question. According to the Remove-AzDataFactoryV2Pipeline doc, the -Force flag simply skips the confirmation prompt. It does not actually 'Force' the deletion in spite of errors.
Since you are already doing automation, might I suggest leveraging the error message to recursively attempt to delete the referencing pipeline. $error[0] gets the most recent error.
(Pseudocode)
try_recurse_delete( pipeline_name )
do_delete(pipeline_name)
if not $error[0].contains("referenced by " + pipeline_name)
then return true
else
try_recurse_delete( get_refrencer_name($error[0]) )
Given that pipeline dependencies can be a many-to-many relationship, subsequent pipelines in your for-each loop might already be deleted by the recursion. You will have to adapt your code to react to 'pipeline not found' type errors.

Related

Pester test doesn't fail with the Array missing values

We are writing Pester test for testing the Azure Resource group to contain certain tags. Following is the script and unfortunately the Pester test is not reporting any failure even after a particular resource group we are checking doesn't contain some of the Tags (from the Array defined). The Pester tests just passes and I am not sure what is that we are doing wrong here.
$resourceGroupName ='DemoRG03032021'
$listOfTags = #('BUSINESS-OWNER','COST-CENTER','LIFECYCLE1', 'APPLICATION','PROJECT-CODE','TECHNICAL-OWNER','BUDGET-CODE')
$checkTags = $false
Describe "Resource Group" {
Context "$resourceGroupName" {
$resourceGroup = Get-AzResourceGroup -Name $resourceGroupName
foreach ($tagName in $listOfTags)
{
It "$($resourceGroup.ResourceGroupName) has a $tagName as tag" {
$resourceGroup.tags.keys -contains $tagName | Should -Be $true
}
}
}
}
In v5 you can now also do it like this which is a bit more readable in my opinion:
BeforeDiscovery {
$listOfTags = #('BUSINESS-OWNER', 'COST-CENTER', 'LIFECYCLE1', 'APPLICATION', 'PROJECT-CODE', 'TECHNICAL-OWNER', 'BUDGET-CODE')
}
BeforeAll {
$resourceGroupName = 'DemoRG03032021'
$resourceGroup = Get-AzResourceGroup -Name $resourceGroupName
}
Describe "Resource Group" -ForEach $listOfTags {
It "$($resourceGroup.ResourceGroupName) has a $_ as tag" {
$resourceGroup.tags.keys -contains $_ | Should -Be $true
}
}
Edit: Putting the answer to your follow up question here as it's a lot more readable.
This is how I would personally organize the code you posted in the comments. I think adding another logical block makes sense, actually. If you put your other It statements inside of the block running with -Foreach then you would run every new test once for every tag in $listOfTags as well which is probably not what you want.
BeforeDiscovery {
$listOfTags = #('BUSINESS-OWNER', 'COST-CENTER', 'LIFECYCLE1', 'APPLICATION', 'PROJECT-CODE', 'TECHNICAL-OWNER', 'BUDGET-CODE')
}
Describe "Resource Group Tests" {
BeforeAll {
$resourceGroupName = 'TestResourceGroup203122021'
$resourceGroupLocation = 'eastus22222'
$resourceGroup = Get-AzResourceGroup -Name $resourceGroupName
}
Context "Resource Group Tags" -ForEach $listOfTags {
It "$($resourceGroup.ResourceGroupName) has a $_ as tag" {
$resourceGroup.tags.keys -contains $_ | Should -Be $true
}
}
Context "Resource Group Attributes" {
It "Resource Group $($resourceGroup.ResourceGroupName) Exists" {
$resourceGroup | Should -Not -BeNullOrEmpty
}
It "$($resourceGroup.ResourceGroupName) Location is $resourceGroupLocation" {
$($resourceGroup.Location) | Should -Be $resourceGroupLocation
}
}
}
Here is another way to think about it. If you wrote the following:
Foreach ($tag in $listOfTags){
Write-Host 'do this thing for each tag'
Write-Host 'do this thing once'
}
Imagine each Write-Host is your It statement. You don't want that second statement inside of the same context as the other, because you don't want it to run once for every value in $listOfTags. You logically separate it with a new Describe or Context block.

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.

Deploy multiple ARM Templates in parallel with PowerShell

I created a Resource Manager Project in Visual Studio 2017 and as result got Deploy-AzureResourceGroup.ps1 that deploys the template to Azure. But I have multiple templates and would like to be able to deploy them in parallel with PowerShell. My current approach iterates over each template and deploys them sequentially what takes a lot of time.
How can I achieve that?
Edit: taking the response from #4c74356b41 into account. I need to execute some more logic as part of the job. Therefore it is not enough just to execute the ResourceGroupDeployment in parallel.
First of all get all relevant templates e.g. via
$armTemplateFiles = Get-ChildItem -Path $PSScriptRoot -Include *.JobTemplate.json -Recurse;
Now iterate over each template file and create a job for each one (these jobs are then executed in parallel)
Code:
foreach ($armTemplateFile in $armTemplateFiles) {
$logic = {
Param(
[object]
[Parameter(Mandatory=$true)]
$ctx,
[object]
[Parameter(Mandatory=$true)]
$armTemplateFile,
[string]
[Parameter(Mandatory=$true)]
$resourceGroupName
)
function Format-ValidationOutput {
param ($ValidationOutput, [int] $Depth = 0)
Set-StrictMode -Off
return #($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { #(' ' * $Depth + ': ' + $_.Message) + #(Format-ValidationOutput #($_.Details) ($Depth + 1)) })
}
# Get related parameters file
$paramTemplateFile = Get-ChildItem -Path $armTemplateFile.FullName.Replace("JobTemplate.json", "JobTemplate.parameters.json")
# Test Deployment
$ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $resourceGroupName `
-TemplateFile $armTemplateFile.FullName `
-TemplateParameterFile $paramTemplateFile.FullName `
-DefaultProfile $ctx)
if ($ErrorMessages) {
Write-Host '', 'Validation returned the following errors:', #($ErrorMessages), '', 'Template is invalid.'
}
else { # Deploy
New-AzureRmResourceGroupDeployment -Name (($armTemplateFile.Name).Split(".")[0] + ((Get-Date).ToUniversalTime()).ToString('MMddHHmm')) `
-ResourceGroupName $resourceGroupName `
-TemplateFile $armTemplateFile.FullName `
-TemplateParameterFile $paramTemplateFile.FullName `
-Force `
-ErrorVariable ErrorMessages `
-DefaultProfile $ctx
if ($ErrorMessages) {
Write-Host '', 'Template deployment returned the following errors:', #(#($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") })
}
}
}
Start-Job $logic -ArgumentList (Get-AzureRmContext), $armTemplateFile, $ResourceGroupName
}
While (Get-Job -State "Running")
{
Start-Sleep 10
Write-Host "Jobs still running..."
}
Get-Job | Receive-Job
Extremely bad solution, a lot of overcomplication.
New-AzureRmResourceGroupDeployment -ResourceGroup xxx -TemplateFile xxx -AsJob
and you need a loop on top of this

Is there option to auto terminate Azure SQL DW

I am using Azure SQL DW which costs more per an hour. So I want to know is there option for auto terminate SQL DW after an hour or so?
You can pause the Azure Data warehouse and then you only pay for the storage used.
You can automate pausing your DWH by using an Azure automation account and a runbook.
This blog explains the process:
https://blogs.msdn.microsoft.com/allanmiller/2017/09/20/pausing-azure-sql-data-warehouse-using-an-automation-runbook/
Markus
Yes, it is possible.
To save costs, you can pause and resume compute resources on-demand. For example, if you won't be using the database during the night and on weekends, you can pause it during those times, and resume it during the day. You won't be charged for DWUs while the database is paused.
When you pause a database:
Compute and memory resources are returned to the pool of available resources in the data center
DWU costs are zero for the duration of the pause.
Data storage is not affected and your data stays intact.
SQL Data Warehouse cancels all running or queued operations.
To pause a database, use the Suspend-AzureRmSqlDatabase cmdlet.
Suspend-AzureRmSqlDatabase –ResourceGroupName "ResourceGroup1" `
–ServerName "Server01" –DatabaseName "Database02"
To start a database, use the Resume-AzureRmSqlDatabase cmdlet.
Resume-AzureRmSqlDatabase –ResourceGroupName "ResourceGroup1" `
–ServerName "Server01" -DatabaseName "Database02"
More information please refer to this official document.
As Markus Bohse said, you also could use Automation to do this.
Note: You also could write a runbook to start your database.
Update: If you want to use java to do this, please refer to this API document.
public void pauseDataWarehouse()
Update:
You also could Rest API to do this. See this link
POST https://management.azure.com/subscriptions/{subscription-id}/resourceGroups/{resource-group-name}/providers/Microsoft.Sql/servers/{server-name}/databases/{database-name}/pause?api-version=2014-04-01-preview HTTP/1.1
You can use runbooks or powershell script to pause dn resume SQL DWH.
Powershell script is below. If you would like "runbook" let me know
[CmdletBinding(DefaultParametersetName='None')]
Param
(
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
[String]
$AzureSubscriptionId,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
[String]
$AzureDataWareHouseList="All",
[Parameter(Mandatory=$true)][ValidateSet("Suspend","Resume")]
[String]
$Action
)
function PauseAzureDWH
{
Param
(
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
[String]
$AzureSubscriptionId,
[Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
[String]
$AzureDataWareHouseList="All",
[Parameter(Mandatory=$true)][ValidateSet("Suspend","Resume")]
[String]
$Action
)
try
{
Login-AzureRmAccount
Select-AzureRmSubscription -SubscriptionId $AzureSubscriptionId
if($AzureDataWareHouseList -ne "All")
{
$AzureDWHList = #()
$AzureDWHTotalList = $AzureDataWareHouseList.Split(",")
foreach($DWHitem in $AzureDWHTotalList)
{
$DWH = "*$DWHitem*"
$DWH = Get-AzureRmResource | Where-Object ResourceName -like $DWH
if($DWH -ne $Null)
{
$dwc = $DWH.ResourceName.split("/")
# splat reused parameter lists
$ThisDW = #{
'ResourceGroupName' = $DWH.ResourceGroupName
'ServerName' = $dwc[0]
'DatabaseName' = $dwc[1]
}
$AzureDWHList += $ThisDW
}
else
{
Write-Warning "Given DataWarehouse '$DWHitem' is not found in given subscription"
}
}
}
else
{
[array]$TotalDataWareHouseList = Get-AzureRmResource | Where-Object ResourceType -EQ "Microsoft.Sql/servers/databases" | Where-Object Kind -Like "*datawarehouse*"
$AzureDWHList = #()
foreach($DWH in $TotalDataWareHouseList)
{
$dwc = $DWH.ResourceName.split("/")
$ThisDW = #{
'ResourceGroupName' = $DWH.ResourceGroupName
'ServerName' = $dwc[0]
'DatabaseName' = $dwc[1]
}
$AzureDWHList += $ThisDW
}
}
<# foreach($AzureDWHItem in $AzureDWHList)
{
if(!(Get-AzureRmResource | ? {$_.Name -eq $AzureDWHItem.ServerName}) )
{
throw " AzureDWH : [$AzureDWHItem] - Does not exist! - please Check your inputs "
}
} #>
if($Action -eq "Suspend")
{
Write-Output "Suspending Azure DataWareHouses";
foreach ($AzureDWH in $AzureDWHList)
{
$status = Get-AzureRmSqlDatabase #AzureDWH | Select Status
if($status.Status -eq "Online")
{
Suspend-AzureRmSqlDatabase #AzureDWH
}
}
}
else
{
Write-Output "Resuming Azure DataWareHouses";
foreach ($AzureDWH in $AzureDWHList)
{
$status = Get-AzureRmSqlDatabase #AzureDWH | Select Status
if($status.Status -eq "Paused")
{
Resume-AzureRmSqlDatabase #AzureDWH
}
}
}
}
catch
{
Write-Error " Exception while getting resource details and writing back to CSV"
Write-Error $_.Exception.message
Write-Error " ErrorStack: $Error[0] "
exit 1
}
}
PauseAzureDWH -AzureSubscriptionId $AzureSubscriptionId -AzureDataWareHouseList $AzureDataWareHouseList -Action $Action

PowerShell - Loop through all SharePoint Website & continue when an error occurred

$ErrorActionPreference = "Continue"
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction "SilentlyContinue"
$webApp = "Https://SharePointSite.com"
$wa = Get-SPWebApplication -identity $webApp
foreach ($site in $wa.Sites) {
foreach ($web in $site.AllWebs) {
$siteURL = $web.Url
Write-Host $siteURL
}
}
The problem is that when it hits the statement foreach ($site in $wa.Sites), and it cannot get the site due to access denied or any error, it would stop. I would like to continue. I tried to TRY CATCH FINALLY, and it still stops when it encounters an error.
I tried to put in -ErrorAction Continue, but it gives me an error message as
you must provide a value expression on the right-hand side of the '-' operator
How do I get around it so it would continue to the site?
I really appreciate your help.
Thanks
If you want to use try..catch, then you must throw terminating errors. Non-terminating errors are not handles by a try..catch block.
try {
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction Stop;
$webApp = "Https://SharePointSite.com"
$wa = Get-SPWebApplication -identity $webApp -ErrorAction Stop;
foreach ($site in $wa.Sites) {
foreach ($web in $site.AllWebs) {
$siteURL = $web.Url
Write-Host $siteURL
}
}
}
catch {
Write-Host -Object ('Error occurred: {0}' -f $_);
}

Resources