PsLogicAppExtractor.psm1
$script:ModuleRoot = $PSScriptRoot $script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\PsLogicAppExtractor.psd1").ModuleVersion # Detect whether at some level dotsourcing was enforced $script:doDotSource = Get-PSFConfigValue -FullName PsLogicAppExtractor.Import.DoDotSource -Fallback $false if ($PsLogicAppExtractor_dotsourcemodule) { $script:doDotSource = $true } <# Note on Resolve-Path: All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator. This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS. Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist. This is important when testing for paths. #> # Detect whether at some level loading individual module files, rather than the compiled module was enforced $importIndividualFiles = Get-PSFConfigValue -FullName PsLogicAppExtractor.Import.IndividualFiles -Fallback $false if ($PsLogicAppExtractor_importIndividualFiles) { $importIndividualFiles = $true } if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true } if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true } function Import-ModuleFile { <# .SYNOPSIS Loads files into the module on module import. .DESCRIPTION This helper function is used during module initialization. It should always be dotsourced itself, in order to proper function. This provides a central location to react to files being imported, if later desired .PARAMETER Path The path to the file to load .EXAMPLE PS C:\> . Import-ModuleFile -File $function.FullName Imports the file stored in $function according to import policy #> [CmdletBinding()] Param ( [string] $Path ) $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath if ($doDotSource) { . $resolvedPath } else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) } } #region Load individual files if ($importIndividualFiles) { # Execute Preimport actions foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) { . Import-ModuleFile -Path $path } # Import all internal functions foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Import all public functions foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Execute Postimport actions foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) { . Import-ModuleFile -Path $path } # End it here, do not load compiled code below return } #endregion Load individual files #region Load compiled code <# This file loads the strings documents from the respective language folders. This allows localizing messages and errors. Load psd1 language files for each language you wish to support. Partial translations are acceptable - when missing a current language message, it will fallback to English or another available language. #> Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'PsLogicAppExtractor' -Language 'en-US' <# ."..\PsLogicAppExtractor\internal\classes\PsLogicAppExtractor.class.ps1" #> class Helper { Helper([object] $values) { if ($values -is [System.Collections.IDictionary]) { foreach ($key in $values.Keys) { if ($this.PSObject.Properties.Item($key)) { $this.$key = $values[$key] } } } else { foreach ($property in $values.PSObject.Properties) { if ($this.PSObject.Properties.Item($property.Name)) { $this.($property.Name) = $property.Value } } } } } class Definition: Helper { [object] ${$schema} [string] $contentVersion [object] $parameters [object] $triggers [object] $actions [object] $outputs Definition([object] $values) : base($values) { } } class Properties : Helper { [string] $state [Definition] $definition [object] $parameters [object] $integrationAccount [object] $accessControl Properties([object] $values) : base($values) { } } class LogicApp : Helper { [string] $type [string] $apiVersion [string] $name [string] $location [object] $tags [object] $identity [Properties] $properties LogicApp([object] $values) : base($values) { } } class ArmTemplate: Helper { [object] ${$schema} [string] $contentVersion [object] $parameters [object] $variables [object] $resources [object] $outputs ArmTemplate([object] $values) : base($values) { } } <# .SYNOPSIS Get the header for a runbook file .DESCRIPTION Gets the header for a runbook file, containing the sane defaults Allows you to prepare the runbook file as much as possible, based on the parameters that you pass to it .PARAMETER SubscriptionId Id of the subscription that you want to work against At runtime / execution of Invoke-PsLaExtractor - your current powershell / az cli session either needs to be "connected" to the subscription or at least have permissions to work against the subscription Useful when you know upfront what you want to work against, as you don't need to pass the parameter into the Invoke-PsLaExtractor .PARAMETER ResourceGroup Name of the resource group that you want to work against At runtime / execution of Invoke-PsLaExtractor - your current powershell / az cli session needs to have permissions to work against the resource group Useful when you know upfront what you want to work against, as you don't need to pass the parameter into the Invoke-PsLaExtractor .PARAMETER Name Name of the logic app, that you want to work against At runtime / execution of Invoke-PsLaExtractor - your current powershell / az cli session needs to have permissions to work against the logic app Useful when you know upfront what you want to work against, as you don't need to pass the parameter into the Invoke-PsLaExtractor .PARAMETER ApiVersion The ApiVersion that you want the LogicApp to be working against The default value is: "2019-05-01" .PARAMETER IncludePrefixSuffix Instruct the cmdlet to add the different prefix and suffix options, with the default values that comes with the module This make it easier to make the runbook file work across different environments, without having to worry about prepping different prefix and suffix value prior .EXAMPLE PS C:\> Get-BuildHeader Creates the bare minimum header for the runbook file Prepares the Properties object with sane defaults, allowing you to edit them after the file has been created .EXAMPLE PS C:\> Get-BuildHeader -SubscriptionId "f5608f3d-ab28-49d8-9b4e-1b1f812d12e0" -ResourceGroup "TestRg" Creates the bare minimum header for the runbook file Prepares the Properties object with SubscriptionId and ResourceGroup, and sane defaults for the remaining objects, allowing you to edit them after the file has been created .EXAMPLE PS C:\> Get-BuildHeader -Name "TestLogicApp" -ApiVersion "2019-05-01" Creates the bare minimum header for the runbook file Prepares the Properties object with Name and ApiVersion, and sane defaults for the remaining objects, allowing you to edit them after the file has been created .NOTES Author: Mötz Jensen (@Splaxi) #> function Get-BuildHeader { [CmdletBinding()] param ( [string] $SubscriptionId, [string] $ResourceGroup, [string] $Name, [string] $ApiVersion = "2019-05-01", [switch] $IncludePrefixSuffix ) $res = New-Object System.Collections.Generic.List[System.Object] $res.Add("# Object to store the needed parameters for when running the export") $res.Add("Properties {") if ($SubscriptionId) { $res.Add('$SubscriptionId = "{0}"' -f $SubscriptionId) } else { $res.Add('$SubscriptionId = $null') } if ($ResourceGroup) { $res.Add('$ResourceGroup = "{0}"' -f $ResourceGroup) } else { $res.Add('$ResourceGroup = $null') } if ($Name) { $res.Add('$Name = "{0}"' -f $Name) } else { $res.Add('$Name = ""') } if ($ApiVersion) { $res.Add('$ApiVersion = "{0}"' -f $ApiVersion) } else { $res.Add('$ApiVersion = ""') } if ($IncludePrefixSuffix) { $res.Add('$Tag_Prefix = "{0}"' -f $(Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.tag.prefix)) $res.Add('$Tag_Suffix = "{0}"' -f $(Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.tag.suffix)) $res.Add('$Parm_Prefix = "{0}"' -f $(Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.parm.prefix)) $res.Add('$Parm_Suffix = "{0}"' -f $(Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.parm.suffix)) $res.Add('$Connection_Prefix = "{0}"' -f $(Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.connection.prefix)) $res.Add('$Connection_Suffix = "{0}"' -f $(Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.connection.suffix)) $res.Add('$Trigger_Prefix = "{0}"' -f $(Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.trigger.prefix)) $res.Add('$Trigger_Suffix = "{0}"' -f $(Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.trigger.suffix)) } $res.Add('}') #Above line completes the Properties declaration $res.Add('') $res.Add('# Used to import the needed classes into the powershell session, to help with the export of the Logic App') $res.Add('."$(Get-PSFConfigValue -FullName PsLogicAppExtractor.ModulePath.Classes)\PsLogicAppExtractor.class.ps1"') $res.Add('') $res.Add("# Path variable for all the tasks that is available from the PsLogicAppExtractor module") $res.Add('$pathTasks = $(Get-PSFConfigValue -FullName PsLogicAppExtractor.ModulePath.Tasks)') $res.Add('') $res.Add("# Include all the tasks that is available from the PsLogicAppExtractor module") $res.Add("Include `"`$pathTasks\All\All.task.ps1`"") $res.Add('') $res } <# .SYNOPSIS Show the window used for OAuth consent .DESCRIPTION Used to handle the consent flow of ApiConnection objects, where the user needs to fill in an user account / credentials It requires human interaction to handle the consent flow, but this cmdlet helps make it a smooth process .PARAMETER Url The url of the endpoint where the consent flow for the specific ApiConnection object can be completed .EXAMPLE PS C:\> Show-OAuthConsentWindow -Url "https://logic-apis-westeurope.consent.azure-apim.net/login?data=eyJMb2dpbklkIjo..." This will invoke the consent flow It will prompt the user to enter an user account / credentials It returns the url, containing the code needed to complete the constent flow against the ApiConnection Object .NOTES This is highly inspired by the previous work of other smart people: https://github.com/logicappsio/LogicAppConnectionAuth/blob/master/LogicAppConnectionAuth.ps1 https://github.com/OfficeDev/microsoft-teams-apps-requestateam/blob/master/Deployment/Scripts/deploy.ps1 Author: Mötz Jensen (@Splaxi) #> function Show-OAuthConsentWindow { [CmdletBinding()] [OutputType('System.String')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] param ( [Alias('Uri')] [string] $Url ) Add-Type -AssemblyName System.Windows.Forms #Building the outer body of the form $form = New-Object -TypeName System.Windows.Forms.Form -Property @{Width = 600; Height = 800 } #Building the inner part of the content on the form $web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{Width = 580; Height = 780; Url = ($url -f ($Scope -join "%20")) } $docComp = { $Global:uri = $web.Url.AbsoluteUri #Close on error or on returned code if ($Global:Uri -match "error=[^&]*|code=[^&]*") { $form.Close() } } #Register the event handler $web.Add_DocumentCompleted($docComp) #Construct the form $form.Controls.Add($web) $form.Add_Shown( { $form.Activate() }) #Display the form $form.ShowDialog() | Out-Null # If below is the URL - the user exited the flow before completing the consent if ($Global:Uri -like "https://login.microsoftonline.com/common/oauth2/authorize*") { "" } else { $Global:Uri } } <# .SYNOPSIS Add new parameter to the ARM template .DESCRIPTION Adds or overwrites an ARM template parameter by the name provided, and allows you to specify the default value, type and the metadata decription Notes: It is considered as an internal function, and should not be used directly. .PARAMETER InputObject The ARM object that you want to work against It has to be a object of the type [ArmTemplate] for it to work properly .PARAMETER Name Name of the parameter that you want to work against If the parameter exists, the value gets overrided otherwise a new parameter is added to the list of parameters .PARAMETER Type The type of the ARM template parameter It supports all known types .PARAMETER Value The default value, that you want to assign to the ARM template parameter .PARAMETER Description The metadata description that you want to assign to the ARM template parameters .EXAMPLE PS C:\> Add-ArmParameter -InputObject $armObj -Name "logicAppName" -Type "string" -Value "TestLogicApp" Creates / updates the logicAppName ARM template parameter Sets the type of the parameter to: string Sets the default value to: TestLogicApp .EXAMPLE PS C:\> Add-ArmParameter -InputObject $armObj -Name "logicAppName" -Type "string" -Value "TestLogicApp" -Description "This is the name we extracted from the orignal LogicApp" Creates / updates the logicAppName ARM template parameter Sets the type of the parameter to: string Sets the default value to: TestLogicApp Sets the metadata description .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Add-ArmParameter { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $InputObject, [Alias('ParameterName')] [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [string] $Type, [Parameter(Mandatory = $true)] [object] $Value, [string] $Description ) if ($Description) { $valueObj = $([ordered]@{ type = $Type; defaultValue = $Value; metadata = [ordered]@{ description = $Description } }) } else { $valueObj = $([ordered]@{ type = $Type; defaultValue = $Value; }) } if ($InputObject.parameters.$Name) { $InputObject.parameters.$Name = $($valueObj) } else { $InputObject.parameters | Add-Member -MemberType NoteProperty -Name $Name -Value $valueObj } $InputObject } <# .SYNOPSIS Add new variable to the ARM template .DESCRIPTION Adds or overwrites an ARM template variable by the name provided, and allows you to specify the value Notes: It is considered as an internal function, and should not be used directly. .PARAMETER InputObject The ARM object that you want to work against It has to be a object of the type [ArmTemplate] for it to work properly .PARAMETER Name Name of the variable that you want to work against If the variable exists, the value gets overrided otherwise a new variable is added to the list of variables .PARAMETER Value The value, that you want to assign to the ARM template variable .EXAMPLE PS C:\> Add-ArmVariable -InputObject $armObj -Name "logicAppName" -Value "TestLogicApp" Creates / updates the logicAppName ARM template variable Sets the value to: TestLogicApp .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Add-ArmVariable { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $InputObject, [Alias('VariableName')] [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [object] $Value ) if ($InputObject.variables.$Name) { $InputObject.variables.$Name = $($Value) } else { $InputObject.variables | Add-Member -MemberType NoteProperty -Name $Name -Value $($Value) } $InputObject } <# .SYNOPSIS Add new parm (parameter) to the LogicApp object .DESCRIPTION Adds or overwrites a LogicApp parm (parameter) by the name provided, and allows you to specify the default value and the type Notes: It is considered as an internal function, and should not be used directly. .PARAMETER InputObject The ARM object that you want to work against It has to be a object of the type [LogicApp] for it to work properly .PARAMETER Name Name of the parm (parameter) that you want to work against If the parm (parameter) exists, the value gets overrided otherwise a new parm (parameter) is added to the list of parms (parameters) .PARAMETER Type The type of the LogicApp parm (parameter) It supports all known types .PARAMETER Value The default value, that you want to assign to the LogicApp parm (parameter) .EXAMPLE PS C:\> Add-LogicAppParm -InputObject $lgObj -Name "TriggerQueue" -Type "string" -Value "Inbound" Creates / updates the TriggerQueue LogicApp parm (parameter) Sets the type of the parameter to: string Sets the default value to: Inbound .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Add-LogicAppParm { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $InputObject, [Alias('ParmName')] [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [string] $Type, [Parameter(Mandatory = $true)] [object] $Value ) $valueObj = $([ordered]@{ type = $Type; defaultValue = $Value; }) if ($InputObject.properties.definition.parameters.$Name) { $InputObject.properties.definition.parameters.$Name = $($valueObj) } else { $InputObject.properties.definition.parameters | Add-Member -MemberType NoteProperty -Name $Name -Value $valueObj } $InputObject } <# .SYNOPSIS Format the name with the prefix and suffix .DESCRIPTION Format the name with the prefix and suffix If the passed prefix and suffix is not $null, then they are used Otherwise the cmdlet will default back to the configuration for each type, that is persisted in the configuration store Notes: It is considered as an internal function, and should not be used directly. .PARAMETER Type The type of name that you want to work against Allowed values: Tag Connection Parameter Parm .PARAMETER Prefix The prefix that you want to append to the name If empty / $null - then the cmdlet will use the prefix that is stored for the specific type .PARAMETER Suffix The suffix that you want to append to the name If empty / $null - then the cmdlet will use the suffix that is stored for the specific type .PARAMETER Value The string value that you want to have the prefix and suffix concatenated with .EXAMPLE PS C:\> Format-Name -Type "Tag" -Value "CostCenter" Formats the value: CostCenter with the default prefix and suffix for the type: Tag The default prefix is: tag_ The default suffix is: $null The output will be: tag_CostCenter .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Format-Name { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')] [CmdletBinding()] param ( [ValidateSet('Tag', 'Connection', 'Parameter', 'Parm', 'Trigger')] [Parameter(Mandatory = $true)] [string] $Type, [string] $Prefix, [string] $Suffix, [Alias('Name')] [Parameter(Mandatory = $true)] [string] $Value ) switch ($Type) { "Tag" { if ($Prefix -or $Suffix) { "$Prefix$Value$Suffix" } else { $Prefix = Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.tag.prefix $Suffix = Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.tag.suffix "$Prefix$Value$Suffix" } } "Connection" { if ($Prefix -or $Suffix) { "$Prefix$Value$Suffix" } else { $Prefix = Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.connection.prefix $Suffix = Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.connection.suffix "$Prefix$Value$Suffix" } } "Parameter" { } "Parm" { if ($Prefix -or $Suffix) { "$Prefix$Value$Suffix" } else { $Prefix = Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.parm.prefix $Suffix = Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.parm.suffix "$Prefix$Value$Suffix" } } "Trigger" { if ($Prefix -or $Suffix) { "$Prefix$Value$Suffix" } else { $Prefix = Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.trigger.prefix $Suffix = Get-PSFConfigValue -FullName PsLogicAppExtractor.prefixsuffix.trigger.suffix "$Prefix$Value$Suffix" } } Default { "$Prefix$Value$Suffix" } } } <# Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.tag.prefix' -Value "tag_" -Initialize -Description "The default prefix for Tag objects, used as a fallback value for the Format-Name cmdlet." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.tag.suffix' -Value "" -Initialize -Description "The default suffix for Tag objects, used as a fallback value for the Format-Name cmdlet. Be default an empty string" Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.tag.prefix' -Value "tag_" -Initialize -Description "The default prefix for Tag objects, used as a fallback value for the Format-Name cmdlet." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.tag.suffix' -Value "" -Initialize -Description "The default suffix for Tag objects, used as a fallback value for the Format-Name cmdlet. Be default an empty string." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.parm.prefix' -Value "parm_" -Initialize -Description "The default prefix for parm (parameter) objects, used as a fallback value for the Format-Name cmdlet." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.parm.suffix' -Value "" -Initialize -Description "The default suffix for parm (parameter) objects, used as a fallback value for the Format-Name cmdlet. Be default an empty string." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.connection.prefix' -Value "connection_" -Initialize -Description "The default prefix for connection objects, used as a fallback value for the Format-Name cmdlet." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.connection.suffix' -Value "_id" -Initialize -Description "The default suffix for connection objects, used as a fallback value for the Format-Name cmdlet." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.trigger.prefix' -Value "trigger_" -Initialize -Description "The default prefix for trigger objects, used as a fallback value for the Format-Name cmdlet." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.trigger.suffix' -Value "" -Initialize -Description "The default suffix for trigger objects, used as a fallback value for the Format-Name cmdlet. Be default an empty string." #> <# .SYNOPSIS Get action from the object, filtered by the type of the action .DESCRIPTION Get actions and all nested actions, filtered by type Notes: It is considered as an internal function, and should not be used directly. .PARAMETER InputObject The object that you want to work against Will by analyzed to see if it has nested actions, and will be recursively traversed to fetch all actions .PARAMETER Type The action type that will be outputted .EXAMPLE PS C:\> Get-ActionsByType -InputObject $obj -Type "Http" Will traverse the $obj and filter actions to only output the ones of the type Http .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Get-ActionsByType { param ( [PsCustomObject] $InputObject, [string] $Type ) if ($InputObject.Type -eq $Type -or $InputObject.Value.Type -eq $Type) { $InputObject } if ($InputObject.Value.actions) { foreach ($item in $InputObject.Value.actions.PsObject.Properties) { Get-ActionsByType -InputObject $item -Type $Type } } elseif ($InputObject.actions) { foreach ($item in $InputObject.actions.PsObject.Properties) { Get-ActionsByType -InputObject $item -Type $Type } } elseif ($InputObject.cases) { foreach ($item in $InputObject.cases.PsObject.Properties) { Get-ActionsByType -InputObject $item -Type $Type } }elseif ($InputObject.Value.cases) { foreach ($item in $InputObject.Value.cases.PsObject.Properties) { Get-ActionsByType -InputObject $item -Type $Type } } } <# .SYNOPSIS Get the value from an ARM template parameter .DESCRIPTION Gets the current default value from the specified ARM template parameter Notes: It is considered as an internal function, and should not be used directly. .PARAMETER InputObject The ARM object that you want to work against It has to be a object of the type [ArmTemplate] for it to work properly .PARAMETER Name Name of the parameter that you want to work against .EXAMPLE PS C:\> Get-ArmParameterValue -InputObject $armObj -Name "logicAppName" Gets the default value from the ARM template parameter: logicAppName .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Get-ArmParameterValue { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $InputObject, [Alias('ParameterName')] [Parameter(Mandatory = $true)] [string] $Name ) if ($InputObject.parameters.$Name) { $InputObject.parameters.$Name.defaultValue } } <# .SYNOPSIS Get the output file .DESCRIPTION Get the full path of the "latest" file from the workpath of the runbook / extraction process Notes: It is considered as an internal function, and should not be used directly. .PARAMETER Path Path to the workpath where the runbook has been persisting files .EXAMPLE PS C:\> Get-ExtractOutput -Path "C:\temp\work_directory" Returns the full path of the latest written file from the "C:\temp\work_directory" path .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Get-ExtractOutput { [CmdletBinding()] param ( [Alias('WorkPath')] [Parameter(Mandatory = $true)] [string] $Path ) $files = Get-ChildItem -Path $Path -Recurse -File $files | Sort-Object -Property LastWriteTime | Select-Object -Last 1 -ExpandProperty FullName } <# .SYNOPSIS Get parameters from ARM template .DESCRIPTION Get parameters from the ARM template You can include / exclude parameters, so your parameter file only contains the parameters you want to handle at deployment The default value is promoted as the initial value of the parameter .PARAMETER Path Path to the ARM template that you want to work against .PARAMETER Exclude Instruct the cmdlet to exclude the given set of parameter names Supports array / list .PARAMETER Include Instruct the cmdlet to include the given set of parameter names Supports array / list .PARAMETER AsFile Instruct the cmdlet to save a valid ARM template parameter file next to the ARM template file .PARAMETER BlankValues Instructs the cmdlet to blank the values in the parameter file .PARAMETER CopyMetadata Instructs the cmdlet to copy over the metadata property from the original parameter in the ARM template, if present .EXAMPLE PS C:\> Get-PsLaArmParameter -Path "C:\temp\work_directory\TestLogicApp.json" Gets all parameters from the "TestLogicApp.json" ARM template The output is written to the console .EXAMPLE PS C:\> Get-PsLaArmParameter -Path "C:\temp\work_directory\TestLogicApp.json" -Exclude "logicAppLocation","trigger_Frequency" Gets all parameters from the "TestLogicApp.json" ARM template Will exclude the parameters "logicAppLocation" & "trigger_Frequency" if present The output is written to the console .EXAMPLE PS C:\> Get-PsLaArmParameter -Path "C:\temp\work_directory\TestLogicApp.json" -Include "trigger_Interval","trigger_Frequency" Gets all parameters from the "TestLogicApp.json" ARM template Will only copy over the parameters "trigger_Interval" & "trigger_Frequency" if present The output is written to the console .EXAMPLE PS C:\> Get-PsLaArmParameter -Path "C:\temp\work_directory\TestLogicApp.json" -AsFile Gets all parameters from the "TestLogicApp.json" ARM template The output is written the "C:\temp\work_directory\TestLogicApp.parameters.json" file .EXAMPLE PS C:\> Get-PsLaArmParameter -Path "C:\temp\work_directory\TestLogicApp.json" -BlankValues Gets all parameters from the "TestLogicApp.json" ARM template Blank all values for each parameter The output is written to the console .EXAMPLE PS C:\> Get-PsLaArmParameter -Path "C:\temp\work_directory\TestLogicApp.json" -CopyMetadata Gets all parameters from the "TestLogicApp.json" ARM template Copies over the metadata property from the original parameter, if present The output is written to the console .NOTES Author: Mötz Jensen (@Splaxi) #> function Get-PsLaArmParameter { [CmdletBinding()] param ( [parameter(Mandatory = $true, ValueFromPipeline = $true)] [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')] [Alias('File')] [string] $Path, [string[]] $Exclude, [string[]] $Include, [switch] $AsFile, [switch] $BlankValues, [switch] $CopyMetadata ) process { $armObj = [ArmTemplate]$(Get-TaskWorkObject -Path $Path) $res = [ordered]@{} $res.'$schema' = "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#" $res.contentVersion = "1.0.0.0" $res.parameters = [ordered]@{} foreach ($item in $armObj.parameters.PsObject.Properties) { if ($item.Name -in $Exclude) { continue } if ($Include.Count -gt 0) { if (-not ($item.Name -in $Include)) { continue } } $valueObj = [ordered]@{} if ($BlankValues) { switch ($item.Value.Type) { "int" { $valueObj.value = 0 } "bool" { $valueObj.value = $false } "object" { $valueObj.value = $null } "array" { $valueObj.value = @() } Default { $valueObj.value = "" } } } else { $valueObj.value = $item.Value.DefaultValue } if ($CopyMetadata -and $item.Value.metadata) { $valueObj.metadata = $item.Value.metadata } $res.parameters."$($item.Name)" = $valueObj } if ($AsFile) { $pathLocal = $Path.Replace(".json", ".parameters.json") $encoding = New-Object System.Text.UTF8Encoding($true) [System.IO.File]::WriteAllLines($pathLocal, $($([PSCustomObject] $res) | ConvertTo-Json -Depth 10), $encoding) Get-Item -Path $pathLocal | Select-Object -ExpandProperty FullName } else { $([PSCustomObject] $res) | ConvertTo-Json -Depth 10 } } } <# .SYNOPSIS Get ManagedApi connection objects .DESCRIPTION Get the ApiConnection objects from a resource group Helps to identity ApiConnection objects that are failed or missing an consent / authentication Uses the current connected Az.Account session to pull the details from the azure portal .PARAMETER SubscriptionId Id of the subscription that you want to work against, your current Az.Account powershell session either needs to be "connected" to the subscription or at least have permissions to work against the subscription .PARAMETER ResourceGroup Name of the resource group that you want to work against, your current powershell session needs to have permissions to work against the resource group .PARAMETER FilterError Filter the list of ApiConnections to a specific error status Valid list of options: 'Unauthorized' 'Unauthenticated' .PARAMETER IncludeStatus Filter the list of ApiConnections to a specific (overall) status Valid list of options: Connected Error .PARAMETER Detailed Instruct the cmdlet to output with the detailed format directly .PARAMETER Tools Instruct the cmdlet which tool to use Options are: AzCli (azure cli) Az.Powershell (Az.Accounts+ PowerShell native modules) .EXAMPLE PS C:\> Get-PsLaManagedApiConnection -ResourceGroup "TestRg" This will fetch all ApiConnection objects from the "TestRg" Resource Group Output example: Name DisplayName OverallStatus Id StatusDetails ---- ----------- ------------- -- ------------- azureblob TestFtpDownload Connected /subscriptions/467c… {"status": "Connect… azureeventgrid TestEventGrid Error /subscriptions/467c… {"status": "Error",… azurequeues Test Connected /subscriptions/467c… {"status": "Connect… office365 MyPersonalCon.. Connected /subscriptions/467c… {"status": "Error",… office365-1 MyPersonalCon.. Connected /subscriptions/467c… {"status": "Connect… .EXAMPLE PS C:\> Get-PsLaManagedApiConnection -ResourceGroup "TestRg" -Detailed This will fetch all ApiConnection objects from the "TestRg" Resource Group It will display detailed information about the ApiConnection object Output example: id : /subscriptions/b466443d-6eac-4513-a7f0-3579502929f00/resourceGroups/TestRg/providers/Microsoft.W eb/connections/azureblob name : azureblob DisplayName : TestFtpDownload AuthenticatedUser : ParameterValues : @{accountName=storageaccount1} OverallStatus : Connected StatusDetails : { "status": "Connected" } id : /subscriptions/b466443d-6eac-4513-a7f0-3579502929f00/resourceGroups/TestRg/providers/Microsoft.W eb/connections/azureeventgrid name : azureeventgrid DisplayName : TestEventGrid AuthenticatedUser : @{name=sarah@contoso.com} ParameterValues : @{token:TenantId=f312ba7d-b63a-4306-9e97-a623c3f42024; token:grantType=code} OverallStatus : Error StatusDetails : { "status": "Error", "target": "token", "error": { "code": "Unauthorized", "message": "Failed to refresh access token for service: aadcertificate. Correlation Id=g-3bdeeea8-ae1f-4ac3-82dc-7fee7f16a1e2, UTC TimeStamp=4/8/2021 11:40:36 PM, Error: Failed to acquire token from AAD: {\"error\":\"invalid_grant\",\"error_description\":\"AADSTS700082: The refresh token has expired due to inactivity.The token was issued on 2020-09-01T12:13:41.5336734Z and was inactive for 90.00:00:00.\\r\\nTrace ID: b6f03183-79e9-4f81-a640-efcf65c30400\\r\\nCorrelation ID: 52b391c3-9c1d-42c7-99f3-a219b7675aee\\r\\nTimestamp: 2021-04-08 23:40:36Z\",\"error_codes\":[700082],\"timestamp\":\"2021-04-08 23:40:36Z\",\"trace_id\":\"b6f03183-79e 9-4f81-a640-efcf65c30400\",\"correlation_id\":\"52b391c3-9c1d-42c7-99f3-a219b7675aee\",\"error_uri\":\" https://login.windows.net/error?code=700082\"}" } } id : /subscriptions/b466443d-6eac-4513-a7f0-3579502929f00/resourceGroups/TestRg/providers/Microsoft.W eb/connections/office365 name : office365 DisplayName : MyPersonalConnection AuthenticatedUser : ParameterValues : OverallStatus : Error StatusDetails : { "status": "Error", "target": "token", "error": { "code": "Unauthenticated", "message": "This connection is not authenticated." } } id : /subscriptions/b466443d-6eac-4513-a7f0-3579502929f00/resourceGroups/TestRg/providers/Microsoft.W ft.Web/connections/office365-1 name : office365-1 DisplayName : MyPersonalConnection2 AuthenticatedUser : @{name=sarah@contoso.com} ParameterValues : OverallStatus : Connected StatusDetails : { "status": "Connected" } .EXAMPLE PS C:\> Get-PsLaManagedApiConnection -ResourceGroup "TestRg" -IncludeStatus Error -Detailed This will fetch all ApiConnection objects from the "TestRg" Resource Group Filters the list to show only the ones with error Output example: id : /subscriptions/b466443d-6eac-4513-a7f0-3579502929f00/resourceGroups/TestRg/providers/Microsoft.W eb/connections/azureeventgrid name : azureeventgrid DisplayName : TestEventGrid AuthenticatedUser : @{name=sarah@contoso.com} ParameterValues : @{token:TenantId=f312ba7d-b63a-4306-9e97-a623c3f42024; token:grantType=code} OverallStatus : Error StatusDetails : { "status": "Error", "target": "token", "error": { "code": "Unauthorized", "message": "Failed to refresh access token for service: aadcertificate. Correlation Id=g-3bdeeea8-ae1f-4ac3-82dc-7fee7f16a1e2, UTC TimeStamp=4/8/2021 11:40:36 PM, Error: Failed to acquire token from AAD: {\"error\":\"invalid_grant\",\"error_description\":\"AADSTS700082: The refresh token has expired due to inactivity.The token was issued on 2020-09-01T12:13:41.5336734Z and was inactive for 90.00:00:00.\\r\\nTrace ID: b6f03183-79e9-4f81-a640-efcf65c30400\\r\\nCorrelation ID: 52b391c3-9c1d-42c7-99f3-a219b7675aee\\r\\nTimestamp: 2021-04-08 23:40:36Z\",\"error_codes\":[700082],\"timestamp\":\"2021-04-08 23:40:36Z\",\"trace_id\":\"b6f03183-79e 9-4f81-a640-efcf65c30400\",\"correlation_id\":\"52b391c3-9c1d-42c7-99f3-a219b7675aee\",\"error_uri\":\" https://login.windows.net/error?code=700082\"}" } } id : /subscriptions/b466443d-6eac-4513-a7f0-3579502929f00/resourceGroups/TestRg/providers/Microsoft.W eb/connections/office365 name : office365 DisplayName : MyPersonalConnection AuthenticatedUser : ParameterValues : OverallStatus : Error StatusDetails : { "status": "Error", "target": "token", "error": { "code": "Unauthenticated", "message": "This connection is not authenticated." } } .EXAMPLE PS C:\> Get-PsLaManagedApiConnection -ResourceGroup "TestRg" -FilterError Unauthenticated -Detailed This will fetch all ApiConnection objects from the "TestRg" Resource Group Filters the list to show only the ones with error of the type Unauthenticated This is useful in combination with the Invoke-PsLaConsent cmdlet Output example: id : /subscriptions/b466443d-6eac-4513-a7f0-3579502929f00/resourceGroups/TestRg/providers/Microsoft.W eb/connections/office365 name : office365 DisplayName : MyPersonalConnection AuthenticatedUser : ParameterValues : OverallStatus : Error StatusDetails : { "status": "Error", "target": "token", "error": { "code": "Unauthenticated", "message": "This connection is not authenticated." } } .EXAMPLE PS C:\> Get-PsLaManagedApiConnection -ResourceGroup "TestRg" -FilterError Unauthorized -Detailed This will fetch all ApiConnection objects from the "TestRg" Resource Group Filters the list to show only the ones with error of the type Unauthenticated Output example: id : /subscriptions/b466443d-6eac-4513-a7f0-3579502929f00/resourceGroups/TestRg/providers/Microsoft.W eb/connections/azureeventgrid name : azureeventgrid DisplayName : TestEventGrid AuthenticatedUser : @{name=sarah@contoso.com} ParameterValues : @{token:TenantId=f312ba7d-b63a-4306-9e97-a623c3f42024; token:grantType=code} OverallStatus : Error StatusDetails : { "status": "Error", "target": "token", "error": { "code": "Unauthorized", "message": "Failed to refresh access token for service: aadcertificate. Correlation Id=g-3bdeeea8-ae1f-4ac3-82dc-7fee7f16a1e2, UTC TimeStamp=4/8/2021 11:40:36 PM, Error: Failed to acquire token from AAD: {\"error\":\"invalid_grant\",\"error_description\":\"AADSTS700082: The refresh token has expired due to inactivity.The token was issued on 2020-09-01T12:13:41.5336734Z and was inactive for 90.00:00:00.\\r\\nTrace ID: b6f03183-79e9-4f81-a640-efcf65c30400\\r\\nCorrelation ID: 52b391c3-9c1d-42c7-99f3-a219b7675aee\\r\\nTimestamp: 2021-04-08 23:40:36Z\",\"error_codes\":[700082],\"timestamp\":\"2021-04-08 23:40:36Z\",\"trace_id\":\"b6f03183-79e 9-4f81-a640-efcf65c30400\",\"correlation_id\":\"52b391c3-9c1d-42c7-99f3-a219b7675aee\",\"error_uri\":\" https://login.windows.net/error?code=700082\"}" } } .EXAMPLE PS C:\> Get-PsLaManagedApiConnection -ResourceGroup "TestRg" -FilterError Unauthenticated | Invoke-PsLaConsent This will fetch all ApiConnection objects from the "TestRg" Resource Group Filters the list to show only the ones with error of the type Unauthenticated Will pipe the objects to Invoke-PsLaConsent, which will prompt you to enter a valid user account / credentials Note: Read more about Invoke-PsLaConsent before running this command, to ensure you understand what it does .NOTES Author: Mötz Jensen (@Splaxi) #> function Get-PsLaManagedApiConnection { [CmdletBinding(DefaultParameterSetName = "ResourceGroup")] param ( [Parameter(Mandatory = $true, ParameterSetName = "Subscription")] [string] $SubscriptionId, [Parameter(Mandatory = $true, ParameterSetName = "ResourceGroup")] [Parameter(Mandatory = $true, ParameterSetName = "Subscription")] [string] $ResourceGroup, [ValidateSet('Unauthorized', 'Unauthenticated')] [string] $FilterError, [ValidateSet('Connected', 'Error')] [string] $IncludeStatus, [switch] $Detailed, [ValidateSet('AzCli', 'Az.Powershell')] [string] $Tools = 'Az.Powershell' ) if ($Tools -eq 'Az.Powershell') { $parms = @{} $parms.ResourceGroupName = $ResourceGroup $parms.ApiVersion = "2018-07-01-preview" $parms.ResourceProviderName = "Microsoft.Web" $parms.ResourceType = "connections" if ($SubscriptionId) { $parms.SubscriptionId = $SubscriptionId } $res = Invoke-AzRestMethod -Method Get @parms if ($null -eq $res) { #TODO! We need to throw an error Throw } $cons = $res.Content | ConvertFrom-Json -Depth 20 | Select-Object -ExpandProperty Value } else { $uri = "/subscriptions/{subscriptionId}/resourceGroups/$ResourceGroup/providers/Microsoft.Web/connections?api-version=2018-07-01-preview" if ($SubscriptionId) { $uri = $uri.Replace("{subscriptionId}", $SubscriptionId) } $res = az rest --url "$uri" | ConvertFrom-Json -Depth 10 if ($null -eq $res) { #TODO! We need to throw an error Throw } $cons = $res | Select-Object -ExpandProperty Value } $temp = $cons | Select-PSFObject -TypeName PsLaExtractor.ManagedConnection -Property id, Name, @{Label = "DisplayName"; Expression = { $_.properties.DisplayName } }, @{Label = "OverallStatus"; Expression = { $_.properties.overallStatus } }, @{Label = "AuthenticatedUser"; Expression = { $_.properties.authenticatedUser } }, @{Label = "ParameterValues"; Expression = { $_.properties.parameterValues } }, @{Label = "StatusDetails"; Expression = { if ($Detailed) { $_.properties.Statuses | ConvertTo-Json -Depth 4 } else { $($_.properties.Statuses | ConvertTo-Json -Depth 4).Replace("`r`n", "").Replace(" ", "") } } } if ($FilterError -or $IncludeStatus) { $filtered = @(foreach ($item in $temp) { $details = $item.StatusDetails | ConvertFrom-Json -Depth 10 if ($FilterError -and $details.error.code -ne $FilterError) { continue } if ($IncludeStatus -and $item.OverallStatus -ne $IncludeStatus) { continue } $item } ) $temp = $filtered } if ($Detailed) { $temp | Select-PSFObject -Property * -TypeName PsLaExtractor.ManagedConnection.Detailed } else { $temp } } <# .SYNOPSIS Get ManagedApi connection objects status .DESCRIPTION Get the status of ApiConnection objects from a resource group Helps to identity ApiConnection objects that are failed or missing an consent / authentication Uses the current connected Az.Account session to pull the details from the azure portal .PARAMETER SubscriptionId Id of the subscription that you want to work against, your current Az.Account powershell session either needs to be "connected" to the subscription or at least have permissions to work against the subscription .PARAMETER ResourceGroup Name of the resource group that you want to work against, your current powershell session needs to have permissions to work against the resource group .PARAMETER IncludeProperties Filter the list of ApiConnections to a specific (overall) status .PARAMETER FilterError Filter the list of ApiConnections to a specific error status Valid list of options: 'Unauthenticated' 'ConfigurationNeeded' .EXAMPLE PS C:\> Get-PsLaManagedApiConnection.Status.ViaGraph.AzAccount -ResourceGroup "TestRg" This will fetch all ApiConnection objects from the "TestRg" Resource Group Output example: Name State Status ApiType AuthenticatedUser ---- ----- ------ ------- ----------------- API-AzureBlob-ManagedIdentity Enabled Ready azureblob .EXAMPLE PS C:\> Get-PsLaManagedApiConnection.Status.ViaGraph.AzAccount -ResourceGroup "TestRg" -FilterError "Unauthenticated" This will fetch all ApiConnection objects from the "TestRg" Resource Group Will filter the list down to only show those which are "Unauthenticated" Output example: Name State Status ApiType AuthenticatedUser ---- ----- ------ ------- ----------------- API-AzureBlob-ManagedIdentity Error Unauthenticated azureblob .EXAMPLE PS C:\> Get-PsLaManagedApiConnection.Status.ViaGraph.AzAccount -ResourceGroup "TestRg" -IncludeProperties | Format-List This will fetch all ApiConnection objects from the "TestRg" Resource Group Will extract the raw properties of the ApiConnection object, and expose them in the output Format-List is needed to display the PropertiesRaw on screen Output example: Id : /subscriptions/b466443d-6eac-4513-a7f0-3579502929f00/resourceGroups/TestRg /providers/Microsoft.Web/connections/API-AzureBlob-ManagedIdentity Name : API-AzureBlob-ManagedIdentity ResourceGroup : TestRg SubscriptionId : b466443d-6eac-4513-a7f0-3579502929f00 DisplayName : API-AzureBlob-ManagedIdentity State : Enabled Status : Ready ApiType : azureblob AuthenticatedUser : ParameterValues : StatusDetailed : {"status": "Ready"} PropertiesRaw : @{displayName=API-AzureBlob-ManagedIdentity; createdTime=22/06/2023 07.07.22; changedTime=22/06/2023 07.07.22; customParameterValues=; authenticatedUser=; connectionState=Enabled; overallStatus=Ready; testRequests=System.Object[]; testLinks=System.Object[]; statuses=System.Object[]; parameterValueSet=; api=} .NOTES Author: Mötz Jensen (@Splaxi) #> function Get-PsLaManagedApiConnection.Status.ViaGraph.AzAccount { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')] [CmdletBinding()] param ( [string] $SubscriptionId, [Parameter(Mandatory = $true)] [string] $ResourceGroup, [switch] $IncludeProperties, [ValidateSet('Unauthenticated', 'ConfigurationNeeded')] [string] $FilterError ) if (-not $SubscriptionId) { $SubscriptionId = (Get-AzContext).Subscription.Id } $SubscriptionId = $SubscriptionId.ToLower() $filter = '' if ($SubscriptionId) { $filter += " and subscriptionId == '$SubscriptionId'" } $filter += " and resourceGroup =~ '$ResourceGroup'" $query = Get-Content "$(Get-PSFConfigValue -FullName PsLogicAppExtractor.ModulePath.Queries)\AzureResourceGraph.ApiConnections.Status.txt" -Raw $query = $query.Replace('##FILTERS##', $filter) if ($IncludeProperties) { $query = $query + ", Props" } $res = (Search-AzGraph -Query $query -First 1000) if ($FilterError) { $res = @( foreach ($item in $res) { if ($item.StatusDetails.error.code -contains $FilterError) { $item } } ) } $res = $res | Select-Object -Property *, @{Label = "StatusDetailed"; Expression = { $($_.StatusDetails | ConvertTo-Json -Depth 4).Replace("`r`n", "").Replace(" ", "") } } -ExcludeProperty StatusDetails if ($IncludeProperties) { $res | Select-PSFObject -Property *, "Props as PropertiesRaw" -TypeName PsLaExtractor.ManagedConnection.Graph.Status -ExcludeProperty Props } else { $res | Select-PSFObject -Property * -TypeName PsLaExtractor.ManagedConnection.Graph.Status } } <# .SYNOPSIS Get ManagedApi connection objects .DESCRIPTION Get the ApiConnection objects from a resource group Helps to identity ApiConnection objects that are orphaned, or which LogicApps is actually using the specific ApiConnection Uses the current connected Az.Account session to pull the details from the azure portal .PARAMETER SubscriptionId Id of the subscription that you want to work against, your current Az.Account powershell session either needs to be "connected" to the subscription or at least have permissions to work against the subscription .PARAMETER ResourceGroup Name of the resource group that you want to work against, your current powershell session needs to have permissions to work against the resource group .PARAMETER Summarized Instruct the cmdlet to output a summarized References column .EXAMPLE PS C:\> Get-PsLaManagedApiConnection.ViaGraph.AzAccount -ResourceGroup "TestRg" This will fetch all ApiConnection objects from the "TestRg" Resource Group Output example: Name ResourceGroup IsReferenced LogicApp ---- ------------- ------------ -------- API-AzureBlob-ManagedIdentity TestRg true LA-TestExport .EXAMPLE PS C:\> Get-PsLaManagedApiConnection.ViaGraph.AzAccount -ResourceGroup "TestRg" -Summarized This will fetch all ApiConnection objects from the "TestRg" Resource Group It will summarize how many LogicApps that is actually using the specific Api Connection Output example: Name ResourceGroup References SubscriptionId ---- ------------- ---------- -------------- API-AzureBlob-ManagedIdentity TestRg 1 b466443d-6eac-4513-a7f0-35795… .NOTES Author: Mötz Jensen (@Splaxi) #> function Get-PsLaManagedApiConnection.ViaGraph.AzAccount { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')] [CmdletBinding()] param ( [string] $SubscriptionId, [Parameter(Mandatory = $true)] [string] $ResourceGroup, [switch] $Summarized ) if (-not $SubscriptionId) { $SubscriptionId = (Get-AzContext).Subscription.Id } $SubscriptionId = $SubscriptionId.ToLower() $filter = '' if ($SubscriptionId) { $filter += " and subscriptionId == '$SubscriptionId'" } $filter += " and resourceGroup =~ '$ResourceGroup'" if ($Summarized) { $query = Get-Content "$(Get-PSFConfigValue -FullName PsLogicAppExtractor.ModulePath.Queries)\AzureResourceGraph.ApiConnections.Summarized.txt" -Raw $query = $query.Replace('##FILTERS##', $filter) (Search-AzGraph -Query $query) | Select-PSFObject -Property *, "LogicAppReferences as References" -TypeName PsLaExtractor.ManagedConnection.Graph.Summarized -ExcludeProperty LogicAppReferences } else { $query = Get-Content "$(Get-PSFConfigValue -FullName PsLogicAppExtractor.ModulePath.Queries)\AzureResourceGraph.ApiConnections.Detailed.txt" -Raw $query = $query.Replace('##FILTERS##', $filter) (Search-AzGraph -Query $query) | Select-PSFObject -Property *, "IsLogicAppReferenced as IsReferenced" -TypeName PsLaExtractor.ManagedConnection.Graph.Base -ExcludeProperty IsLogicAppReferenced } return } <# .SYNOPSIS Get Managed Api Connection objects by their usage .DESCRIPTION Get Managed Api Connection objects, and filter by their usage You can list Api Connections that are NOT referenced by any Logic App You can list Logic Apps and the Api Connections they reference, but doesn't exists You can list Api Connections that are referenced by Logic Apps .PARAMETER SubscriptionId Id of the subscription that you want to work against, your current az cli session either needs to be "connected" to the subscription or at least have permissions to work against the subscription .PARAMETER ResourceGroup Name of the resource group that you want to work against, your current az cli session needs to have permissions to work against the resource group .PARAMETER IncludeUsed Instruct the cmdlet to include Api Connections that are referenced by Logic Apps .PARAMETER IncludeLogicAppResourceNotFound Instructions the cmdlet to include Api Connections that are referenced by Logic Apps, but where the Api Connection doesn't exists .PARAMETER Tools Instruct the cmdlet which tool to use Options are: AzCli (azure cli) Az.Powershell (Az.Accounts+ PowerShell native modules) .EXAMPLE PS C:\> Get-PsLaManagedApiConnectionByUsage -SubscriptionId "b466443d-6eac-4513-a7f0-3579502929f00" -ResourceGroup "TestRg" This will list all Api Connections in the resource group "TestRg" in the subscription "b466443d-6eac-4513-a7f0-3579502929f00". It will only list Api Connections that are NOT referenced by any Logic App .EXAMPLE PS C:\> Get-PsLaManagedApiConnectionByUsage -SubscriptionId "b466443d-6eac-4513-a7f0-3579502929f00" -ResourceGroup "TestRg" -IncludeUsed This will list all Api Connections in the resource group "TestRg" in the subscription "b466443d-6eac-4513-a7f0-3579502929f00". It will list Api Connections that are NOT referenced by any Logic App. It will list Api Connections that are referenced by Logic Apps. .EXAMPLE PS C:\> Get-PsLaManagedApiConnectionByUsage -SubscriptionId "b466443d-6eac-4513-a7f0-3579502929f00" -ResourceGroup "TestRg" -IncludeLogicAppResourceNotFound This will list all Api Connections in the resource group "TestRg" in the subscription "b466443d-6eac-4513-a7f0-3579502929f00". It will list Api Connections that are NOT referenced by any Logic App. It will list Api Connections that are referenced by Logic Apps, but where the Api Connection doesn't exists. .NOTES Author: Mötz Jensen (@Splaxi) The implementation was inspired by the following blog post: https://techcommunity.microsoft.com/t5/integrations-on-azure-blog/use-powershell-script-to-manage-your-api-connection-of-logic-app/ba-p/2668253 https://www.integration-playbook.io/docs/find-orphaned-api-connectors https://github.com/sandroasp/Azure-Learning-Path/blob/main/Logic-Apps/Find-Azure-Orphaned-API-Connectors-powershell/Find-Orphaned-API-Connectors.ps1 #> function Get-PsLaManagedApiConnectionByUsage { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')] [CmdletBinding(DefaultParameterSetName = "ResourceGroup")] param ( [Parameter(Mandatory = $true, ParameterSetName = "Subscription")] [string] $SubscriptionId, [Parameter(Mandatory = $true, ParameterSetName = "ResourceGroup")] [Parameter(Mandatory = $true, ParameterSetName = "Subscription")] [string] $ResourceGroup, [switch] $IncludeUsed, [switch] $IncludeLogicAppResourceNotFound, [ValidateSet('AzCli', 'Az.Powershell')] [string] $Tools = 'Az.Powershell' ) # Uri for the Azure REST API, to get Api Connections and Logic Apps $uriConnetions = "/subscriptions/{subscriptionId}/resourceGroups/$ResourceGroup/providers/Microsoft.Web/connections?api-version=2018-07-01-preview" $uriWorkflows = "/subscriptions/{subscriptionId}/resourceGroups/$ResourceGroup/providers/Microsoft.Logic/workflows?api-version=2018-07-01-preview" if ($SubscriptionId) { $uriConnetions = $uriConnetions.Replace("{subscriptionId}", $SubscriptionId) $uriWorkflows = $uriWorkflows.Replace("{subscriptionId}", $SubscriptionId) } [System.Collections.Generic.List[System.Object]] $resArray = @() $localUri = $uriConnetions do { # Fething the Api Connections # The do while supports paging / nextLink to load all objects from the resource group if ($Tools -eq 'Az.Powershell') { $resGet = Invoke-AzRest -Path "$localUri" -Method GET | Select-Object -ExpandProperty Content | ConvertFrom-Json -Depth 100 } else { $resGet = az rest --url "$localUri" | ConvertFrom-Json -Depth 100 } $resArray.AddRange($resGet.Value) if ($($resGet.nextLink) -match ".*(/subscriptions/.*)") { $localUri = $Matches[1] } } while ($resGet.nextLink) # Persist the Api Connections $resConnections = $resArray.ToArray() [System.Collections.Generic.List[System.Object]] $resArray = @() $localUri = $uriWorkflows do { # Fething the Logic Apps # The do while supports paging / nextLink to load all objects from the resource group if ($Tools -eq 'Az.Powershell') { $resLocal = Invoke-AzRest -Path "$localUri" -Method GET | Select-Object -ExpandProperty Content } else { $resLocal = az rest --url "$localUri" } # Hack to handle any errors in the response, which cannot be handled by the ConvertFrom-Json $resGet = $resLocal.Replace('""', '"Dummy"') | ConvertFrom-Json -Depth 100 $resArray.AddRange($resGet.Value) if ($($resGet.nextLink) -match ".*(/subscriptions/.*)") { $localUri = $Matches[1] } # } while (1 -eq 2) } while ($resGet.nextLink) # Persist the Logic Apps $resWorkflows = $resArray.ToArray() if ($null -eq $resConnections -or $null -eq $resWorkflows) { #TODO! We need to throw an error Throw } # Create an array of all Api Connections available $availableConnections = @($resConnections.id) [System.Collections.Generic.List[System.Object]] $colUsedConnections = @() [System.Collections.Generic.List[System.Object]] $colNonExistingConnections = @() # Loop through all Logic Apps foreach ($la in $resWorkflows) { # Not all Logic Apps have a connection reference if ($null -eq $la.properties.parameters.'$connections') { continue } # Loop through all connections of the Logic App foreach ($connection in $la.properties.parameters.'$connections'.value.PsObject.Properties) { if ($connection.value.connectionId -in $availableConnections) { # The connection was found in the available connections $colUsedConnections.Add( [PSCustomObject]@{ Id = $connection.value.connectionId Connection = $resConnections | Where-Object { $_.id -eq $connection.value.connectionId } | Select-Object -First 1 LogicApp = $la.Name } ) } else { # The connection was not found in the available connections $colNonExistingConnections.Add( [PSCustomObject]@{ LogicApp = $la.Name ConnectionDetails = $connection } ) } } } [System.Collections.Generic.List[System.Object]] $resArray = @() if ($IncludeUsed -and $colUsedConnections.Count -gt 0) { # Should we add all used connections? $resArray.AddRange( ($colUsedConnections.ToArray() | Select-PSFObject -TypeName PsLaExtractor.ManagedConnection.Usage -Property Id, @{Label = "Name"; Expression = { $_.Connection.Name } }, @{Label = "DisplayName"; Expression = { $_.Connection.Properties.DisplayName } }, LogicApp, @{Label = "Usage"; Expression = { "LogicAppReferenced" } }, @{Label = "Location"; Expression = { $_.Connection.Location } }, @{Label = "Type"; Expression = { $_.Connection.Properties.Api.Name } }, @{Label = "Properties"; Expression = { $_.Connection.Properties } }) ) } if ($IncludeLogicAppResourceNotFound -and $colUsedConnections.Count -gt 0) { # Should we add all connections, that Logic Apps are referencing, but are not available? $resArray.AddRange( ($colNonExistingConnections.ToArray() | Select-PSFObject -TypeName PsLaExtractor.ManagedConnection.Usage -Property @{Label = "Id"; Expression = { $_.ConnectionDetails.Value.connectionId } }, @{Label = "Name"; Expression = { $_.ConnectionDetails.Name } }, @{Label = "DisplayName"; Expression = { $_.ConnectionDetails.Value.connectionName } }, LogicApp, @{Label = "Usage"; Expression = { "ResourceNotFound" } }, @{Label = "Location"; Expression = { ($_.ConnectionDetails.Value.Id | Select-String -Pattern "/locations/(.*?)/").Matches[0].Groups[1].Value } }, @{Label = "Type"; Expression = { $_.ConnectionDetails.Value.Id.Split("/") | Select-Object -Last 1 } }) ) } $resConnections | Where-Object { -not ($_.id -in $colUsedConnections.Id) } | Foreach-object { # Add all connections, that are not used by any Logic App $resArray.Add( [PSCustomObject]@{ Id = $_.id Name = $_.name DisplayName = $_.properties.displayName LogicApp = $null Usage = "NotUsed" Location = $_.location Type = $_.properties.api.name Properties = $_.properties } ) } $resArray } <# .SYNOPSIS Get tasks that are part of the module .DESCRIPTION List all avaiable tasks that are part of the module, to be used for exporting, sanitizing and converting a LogicApp into a deployable ARM template .PARAMETER Category Used to filter the number of tasks down to only being part of the category that you are looking for .PARAMETER Detailed Instruct the cmdlet to output the details about the tasks in a more detailed fashion, makes it easier to read the descriptions for each task .EXAMPLE PS C:\> Get-PsLaTask List all available tasks Output example: Category Name Description -------- ---- ----------- Arm Set-Arm.Connections.ManagedApis.AsParameter Loops all $connections childs… Arm Set-Arm.Connections.ManagedApis.AsVariable Loops all $connections childs… Arm Set-Arm.Connections.ManagedApis.IdFormatted Loops all $connections childs… Arm Set-Arm.IntegrationAccount.IdFormatted.Simple.AsParameter.AsVariable Creates an Arm variable: integrationAccount… .EXAMPLE PS C:\> Get-PsLaTask -Category Converter List all available tasks, which are in the Converter category Output example: Category Name Description -------- ---- ----------- Converter ConvertTo-Arm Converts the LogicApp json structure into a valid ARM template json Converter ConvertTo-Raw Converts the raw LogicApp json structure into the a valid LogicApp json,… .EXAMPLE PS C:\> Get-PsLaTask -Detailed List all available tasks, and outputs it in the detailed view Output example: Category : Arm Name : Set-Arm.Connections.ManagedApis.AsParameter Description : Loops all $connections childs -Creates an Arm parameter, with prefix & suffix --Sets the default value to the original name, extracted from connectionId property -Sets the connectionId to: [resourceId('Microsoft.Web/connections', parameters('XYZ'))] -Sets the connectionName to: [parameters('XYZ')] Category : Arm Name : Set-Arm.Connections.ManagedApis.AsVariable Description : Loops all $connections childs -Creates an Arm variable, with prefix & suffix --Sets the value to the original name, extracted from connectionId property -Sets the connectionId to: [resourceId('Microsoft.Web/connections', variables('XYZ'))] -Sets the connectionName to: [variables('XYZ')] .NOTES Author: Mötz Jensen (@Splaxi) #> function Get-PsLaTask { [CmdletBinding()] param ( [ValidateSet('Arm', 'Converter', 'Exporter', 'Raw')] [string] $Category, [switch] $Detailed ) $res = Get-PsLaTaskByPath -Path "$($MyInvocation.MyCommand.Module.ModuleBase)\internal\tasks" | Where-Object Category -like "*$Category*" | Select-Object -Property * -ExcludeProperty Path if ($Detailed) { $res | Format-List } else { $res } } <# .SYNOPSIS Get tasks that are references from a file .DESCRIPTION Get tasks that are references from a runbook file, to make it easier to understand what a given runbook file is doing .PARAMETER File Path to the runbook file, that you want to analyze .PARAMETER Category Used to filter the number of tasks down to only being part of the category that you are looking for .PARAMETER Detailed Instruct the cmdlet to output the details about the tasks in a more detailed fashion, makes it easier to read the descriptions for each task .EXAMPLE PS C:\> Get-PsLaTaskByFile -File "C:\temp\LogicApp.ExportOnly.psakefile.ps1" List all tasks that are referenced from the file The file needs to be a valid PSake runbook file Output example: Category Name Description -------- ---- ----------- Converter ConvertTo-Arm Converts the LogicApp json structure into a valid ARM template json Converter ConvertTo-Raw Converts the raw LogicApp json structure into the a valid LogicApp json,… Exporter Export-LogicApp.AzCli Exports the raw version of the Logic App from the Azure Portal .EXAMPLE PS C:\> Get-PsLaTaskByFile -File "C:\temp\LogicApp.ExportOnly.psakefile.ps1" -Category Converter List all tasks that are referenced from the file, which are in the Converter category The file needs to be a valid PSake runbook file Output example: Category Name Description -------- ---- ----------- Converter ConvertTo-Arm Converts the LogicApp json structure into a valid ARM template json Converter ConvertTo-Raw Converts the raw LogicApp json structure into the a valid LogicApp json,… .EXAMPLE PS C:\> Get-PsLaTaskByFile -File "C:\temp\LogicApp.ExportOnly.psakefile.ps1" -Detailed List all tasks that are referenced from the file, and outputs it in the detailed view The file needs to be a valid PSake runbook file Output example: Category : Converter Name : ConvertTo-Arm Description : Converts the LogicApp json structure into a valid ARM template json Category : Converter Name : ConvertTo-Raw Description : Converts the raw LogicApp json structure into the a valid LogicApp json, this will remove different properties that are not needed Category : Exporter Name : Export-LogicApp.AzCli Description : Exports the raw version of the Logic App from the Azure Portal .NOTES Author: Mötz Jensen (@Splaxi) #> function Get-PsLaTaskByFile { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')] [CmdletBinding()] param ( [parameter(Mandatory = $true, ValueFromPipeline = $true)] [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')] [Alias('Runbook')] [string] $File, [ValidateSet('Arm', 'Converter', 'Exporter', 'LogicApp')] [string] $Category, [switch] $Detailed ) process { # We are playing around with the internal / global psake object $psake.context = New-Object System.Collections.Stack $res = @(Get-PSakeScriptTasks -Runbook $File | Where-Object Name -ne "Default" | Select-Object -Property @{Label = "Category"; Expression = { $_.Alias.Split(".")[0] } }, Name, Description) $temp = $res | Where-Object Category -like "*$Category*" | Sort-Object Category, Name if ($Detailed) { $temp | Format-List } else { $temp } } } <# .SYNOPSIS Get tasks based on files from a directory .DESCRIPTION Get tasks from individual files, that are located in a directory .PARAMETER Path Path to the directory where there are valid PSake tasks saved as ps1 files .EXAMPLE PS C:\> Get-PsLaTaskByPath -Path c:\temp\tasks List all available tasks, based on the files in the directory All files has to be valid PSake files saved as ps1 files Output example: Category : Arm Name : Set-Arm.Connections.ManagedApis.AsParameter Description : Loops all $connections childs -Creates an Arm parameter, with prefix & suffix --Sets the default value to the original name, extracted from connectionId property -Sets the connectionId to: [resourceId('Microsoft.Web/connections', parameters('XYZ'))] -Sets the connectionName to: [parameters('XYZ')] Path : c:\temp\tasks\Set-Arm.Connections.ManagedApis.AsParameter.task.ps1 File : Set-Arm.Connections.ManagedApis.AsParameter.task.ps1 Category : Arm Name : Set-Arm.Connections.ManagedApis.AsVariable Description : Loops all $connections childs -Creates an Arm variable, with prefix & suffix --Sets the value to the original name, extracted from connectionId property -Sets the connectionId to: [resourceId('Microsoft.Web/connections', variables('XYZ'))] -Sets the connectionName to: [variables('XYZ')] Path : c:\temp\tasks\Set-Arm.Connections.ManagedApis.AsVariable.task.ps1 File : Set-Arm.Connections.ManagedApis.AsVariable.task.ps1 .NOTES General notes #> function Get-PsLaTaskByPath { [CmdletBinding()] param ( [parameter(Mandatory = $true, ValueFromPipeline = $true)] [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')] [string] $Path ) process { $files = Get-ChildItem -Path "$Path\*.ps1" $res = New-Object System.Collections.Generic.List[System.Object] # We are playing around with the internal / global psake object $psake.context = New-Object System.Collections.Stack $psake.context.push( @{ "tasks" = @{} "aliases" = @{} } ) foreach ($item in $files) { # We are playing around with the internal / global psake object $psake.context = New-Object System.Collections.Stack $psake.context.push( @{ "tasks" = @{} "aliases" = @{} } ) . $item.FullName foreach ($task in $psake.context.tasks) { foreach ($value in $task.Values) { $res.Add([PsCustomObject][ordered]@{ Category = $value.Alias.Split(".")[0] Name = $value.Name Description = $value.Description Path = $item.FullName File = $item.Name }) } } } # We are playing around with the internal / global psake object $psake.context = New-Object System.Collections.Stack $res.ToArray() | Where-Object Name -ne "Default" | Sort-Object Category, Name } } <# .SYNOPSIS Get tasks that are references from a file, with the execution order .DESCRIPTION Get tasks that are references from a runbook file, to make it easier to understand what a given runbook file is doing Includes the execution order of the tasks, to visualize the tasks sequence .PARAMETER File Path to the runbook file, that you want to analyze .PARAMETER Detailed Instruct the cmdlet to output the details about the tasks in a more detailed fashion, makes it easier to read the descriptions for each task .EXAMPLE PS C:\> Get-PsLaTaskOrderByFile -File "C:\temp\LogicApp.ExportOnly.psakefile.ps1" List all tasks that are referenced from the file The file needs to be a valid PSake runbook file Output example: ExecutionOrder Category Name Description -------------- -------- ---- ----------- 1 Exporter Export-LogicApp.AzCli Exports the raw version of the Logic App from the Azure Portal 2 Converter ConvertTo-Raw Converts the raw LogicApp json structure into the a valid LogicApp j… 3 Converter ConvertTo-Arm Converts the LogicApp json structure into a valid ARM template json .EXAMPLE PS C:\> Get-PsLaTaskOrderByFile -File "C:\temp\LogicApp.ExportOnly.psakefile.ps1" -Detailed List all tasks that are referenced from the file, and outputs it in the detailed view The file needs to be a valid PSake runbook file Output example: ExecutionOrder : 1 Category : Exporter Name : Export-LogicApp.AzCli Description : Exports the raw version of the Logic App from the Azure Portal ExecutionOrder : 2 Category : Converter Name : ConvertTo-Raw Description : Converts the raw LogicApp json structure into the a valid LogicApp json, this will remove different properties that are not needed ExecutionOrder : 3 Category : Converter Name : ConvertTo-Arm Description : Converts the LogicApp json structure into a valid ARM template json .NOTES General notes #> function Get-PsLaTaskOrderByFile { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')] [CmdletBinding()] param ( [parameter(Mandatory = $true, ValueFromPipeline = $true)] [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')] [Alias('Runbook')] [string] $File, [switch] $Detailed ) process { # We are playing around with the internal / global psake object $psake.context = New-Object System.Collections.Stack $default = Get-PSakeScriptTasks -Runbook $File | Where-Object Name -eq "Default" | Select-Object -First 1 $tasks = @(Get-PSakeScriptTasks -Runbook $File | Where-Object Name -ne "Default" | Select-Object -Property @{Label = "Category"; Expression = { $_.Alias.Split(".")[0] } }, Name, Description) $res = @(for ($i = 0; $i -lt $default.DependsOn.Count; $i++) { $tasks | Where-Object Name -eq $($default.DependsOn[$i]) | Select-Object -Property @{Label = "ExecutionOrder"; Expression = { $i + 1 } }, * }) if ($Detailed) { $res | Format-List } else { $res } } } <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Category Instruct the cmdlet which template type you want to have outputted .PARAMETER OutputPath Path to were the Task template file will be persisted The path has to be a directory The file will be named: _set-XYA.Template.ps1 .EXAMPLE PS C:\> Get-PsLaTaskTemplate -Category "Arm" Outputs the task template of the type Arm to the console .EXAMPLE PS C:\> Get-PsLaTaskTemplate -Category "Arm" -OutputPath "C:\temp\work_directory" Outputs the task template of the type Arm Persists the file into the "C:\temp\work_directory" directory .NOTES Author: Mötz Jensen (@Splaxi) #> function Get-PsLaTaskTemplate { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [ValidateSet('Arm', 'Converter', 'Raw')] [string] $Category, [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')] [string] $OutputPath ) if ($OutputPath) { Copy-Item -Path "$($script:ModuleRoot)\internal\tasks\_Set-$Category.Template.tmp" -Destination "$OutputPath\_Set-$Category.Template.ps1" -PassThru -Force | Select-Object -ExpandProperty FullName } else { Get-Content -Path "$($script:ModuleRoot)\internal\tasks\_Set-$Category.Template.tmp" -Raw } } <# .SYNOPSIS Get the object that the task has to work against .DESCRIPTION Gets the object from the "previous" task, based on the persisted path and loads it into memory using ConvertFrom-Json Notes: It is considered as an internal function, and should not be used directly. .PARAMETER Path Path to the file that you want the task to work against .EXAMPLE PS C:\> Get-TaskWorkObject Returns the object that is stored at the location passed in the Path parameter .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Get-TaskWorkObject { [CmdletBinding()] param ( [string] $Path = $(Get-PSFConfigValue -FullName PsLogicAppExtractor.Execution.TaskInputNext) ) Get-Content -Path $Path -Raw | ConvertFrom-Json -Depth 100 } <# .SYNOPSIS Get the object that the task has to work against, as a raw string .DESCRIPTION Gets the object from the "previous" task, based on the persisted path and loads it into memory using Get-Content Notes: It is considered as an internal function, and should not be used directly. .PARAMETER Path Path to the file that you want the task to work against .EXAMPLE PS C:\> Get-TaskWorkObject Returns the object that is stored at the location passed in the Path parameter .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Get-TaskWorkRaw { [CmdletBinding()] param ( [string] $Path = $(Get-PSFConfigValue -FullName PsLogicAppExtractor.Execution.TaskInputNext) ) Get-Content -Path $Path -Raw } <# .SYNOPSIS Start the consent flow for an ApiConnection object .DESCRIPTION Some of the ApiConnection objects needs an user account to consent / authenticate, before it works This cmdlet helps starting, running and completing the consent flow an ApiConnection object Uses the current connected Az.Account session to pull the details from the azure portal .PARAMETER Id The (resource) id of the ApiConnection object that you want to work against, your current Az.Account powershell session either needs to be "connected" to the subscription/resource group or at least have permissions to work against the subscription/resource group, where the ApiConnection object is located .EXAMPLE PS C:\> Invoke-PsLaConsent.AzAccount -Id "/subscriptions/b466443d-6eac-4513-a7f0-3579502929f00/providers/Microsoft.Web/locations/westeurope/managedApis/servicebus" This will start the consent flow for the ApiConnection object It will prompt the user to fill in an account / credential It will confirm the consent directly to the ApiConnection object .EXAMPLE PS C:\> Get-PsLaManagedApiConnection.AzAccount -ResourceGroup "TestRg" -FilterError Unauthenticated | Invoke-PsLaConsent.AzAccount This will fetch all ApiConnection objects from the "TestRg" Resource Group Filters the list to show only the ones with error of the type Unauthenticated Will pipe the objects to Invoke-PsLaConsent.AzAccount This will start the consent flow for the ApiConnection object It will prompt the user to fill in an account / credential It will confirm the consent directly to the ApiConnection object .NOTES This is highly inspired by the previous work of other smart people: https://github.com/logicappsio/LogicAppConnectionAuth/blob/master/LogicAppConnectionAuth.ps1 https://github.com/OfficeDev/microsoft-teams-apps-requestateam/blob/master/Deployment/Scripts/deploy.ps1 Author: Mötz Jensen (@Splaxi) #> function Invoke-PsLaConsent.AzAccount { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [Alias("ResourceId")] [string] $Id ) begin { $listParms = @{ "parameters" = , @{ "parameterName" = "token"; "redirectUrl" = "http://localhost" } } } process { if (-not ($Id -match "/Microsoft.Web/connections/(.*)")) { $messageString = "The resource id supplied didn't match the expected structure. Please make sure the resource id is a <c='em'>Microsoft.Web/connections</c>." Write-PSFMessage -Level Host -Message $messageString Stop-PSFFunction -Message "Stopping because the resource id did match." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', ''))) return } Write-PSFMessage -Level Host -Message "You will be prompted to consent the ApiConnection object: <c='em'>$($Matches[1])</c>. You might need to supplied credentials that <c='em'>are different</c> from your personal account / credentials." # Get the links needed for consent $consentResponse = Invoke-AzResourceAction -Action "listConsentLinks" -ResourceId $Id -Parameters $listParms -Force # Show sign-in prompt window and grab the code after authentication $resUrl = Show-OAuthConsentWindow -URL $consentResponse.Value.Link if ([System.String]::IsNullOrEmpty($resUrl)) { $messageString = "It seems that either the consent failed or you exited the consent flow before it completed. Please make sure to complete the consent flow all the way through for this to work." Write-PSFMessage -Level Host -Message $messageString Stop-PSFFunction -Message "Stopping because result from the consent flow was empty." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', ''))) return } $regex = '(code=)(.*)$' $code = ($resUrl | Select-string -pattern $regex).Matches[0].Groups[2].Value if ($code) { $confirmParms = @{ } $confirmParms.Add("code", $code) # NOTE: errors ignored as this appears to error due to a null response Invoke-AzResourceAction -Action "confirmConsentCode" -ResourceId $Id -Parameters $confirmParms -Force -ErrorAction Ignore } } } <# .SYNOPSIS Start the consent flow for an ApiConnection object .DESCRIPTION Some of the ApiConnection objects needs an user account to consent / authenticate, before it works This cmdlet helps starting, running and completing the consent flow an ApiConnection object Uses the current connected az cli session to pull the details from the azure portal .PARAMETER Id The (resource) id of the ApiConnection object that you want to work against, your current az cli session either needs to be "connected" to the subscription/resource group or at least have permissions to work against the subscription/resource group, where the ApiConnection object is located .EXAMPLE PS C:\> Invoke-PsLaConsent.AzCli -Id "/subscriptions/b466443d-6eac-4513-a7f0-3579502929f00/providers/Microsoft.Web/locations/westeurope/managedApis/servicebus" This will start the consent flow for the ApiConnection object It will prompt the user to fill in an account / credential It will confirm the consent directly to the ApiConnection object .EXAMPLE PS C:\> Get-PsLaManagedApiConnection.AzCli -ResourceGroup "TestRg" -FilterError Unauthenticated | Invoke-PsLaConsent.AzCli This will fetch all ApiConnection objects from the "TestRg" Resource Group Filters the list to show only the ones with error of the type Unauthenticated Will pipe the objects to Invoke-PsLaConsent.AzCli This will start the consent flow for the ApiConnection object It will prompt the user to fill in an account / credential It will confirm the consent directly to the ApiConnection object .NOTES This is highly inspired by the previous work of other smart people: https://github.com/logicappsio/LogicAppConnectionAuth/blob/master/LogicAppConnectionAuth.ps1 https://github.com/OfficeDev/microsoft-teams-apps-requestateam/blob/master/Deployment/Scripts/deploy.ps1 Author: Mötz Jensen (@Splaxi) #> function Invoke-PsLaConsent.AzCli { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [Alias("ResourceId")] [string] $Id ) begin { #! Hack to make the json a single line / oneline # https://github.com/Azure/azure-cli/issues/13056 $payloadList = $([PSCustomObject]@{ "parameters" = , @{ "parameterName" = "token"; "redirectUrl" = "http://localhost" } } | ConvertTo-Json -Depth 10).Replace("`r`n", "").Replace('"', '\"') } process { if (-not ($Id -match "/Microsoft.Web/connections/(.*)")) { $messageString = "The resource id supplied didn't match the expected structure. Please make sure the resource id is a <c='em'>Microsoft.Web/connections</c>." Write-PSFMessage -Level Host -Message $messageString Stop-PSFFunction -Message "Stopping because the resource id did match." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', ''))) return } Write-PSFMessage -Level Host -Message "You will be prompted to consent the ApiConnection object: <c='em'>$($Matches[1])</c>. You <c='em'>might</c> need to supplied credentials that <c='em'>are different</c> from your personal account / credentials." $uriList = "$Id/listConsentLinks?api-version=2018-07-01-preview" # Get the links needed for consent $consentResponse = az rest --method POST --url "$uriList" --body $payloadList --query "value | [0]" | ConvertFrom-Json -Depth 10 # Show sign-in prompt window and grab the code after authentication $resUrl = Show-OAuthConsentWindow -URL $consentResponse.Link if ([System.String]::IsNullOrEmpty($resUrl)) { $messageString = "It seems that either the consent failed or you exited the consent flow before it completed. Please make sure to <c='em'>complete</c> the consent flow all the way through for this to work." Write-PSFMessage -Level Host -Message $messageString Stop-PSFFunction -Message "Stopping because result from the consent flow was empty." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', ''))) return } $regex = '(code=)(.*)$' $code = ($resUrl | Select-string -pattern $regex).Matches[0].Groups[2].Value if ($code) { $payloadConfirm = $([PSCustomObject]@{code = $code } | ConvertTo-Json -Depth 10).Replace("`r`n", "").Replace('"', '\"') $uriConfirm = "$Id/confirmConsentCode?api-version=2018-07-01-preview" az rest --method POST --url "$uriConfirm" --body $payloadConfirm | ConvertFrom-Json -Depth 10 if ($LastExitCode -ne 0) { $messageString = "There was an error while posting the <c='em'>confirmConsentCode</c> on the ApiConnection object. Try again and make sure that you used a <c='em'>user account / credential</c>." Write-PSFMessage -Level Host -Message $messageString Stop-PSFFunction -Message "Stopping because the posting of the confirmConsentCode failed." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', ''))) return } } } } <# .SYNOPSIS Execute the extractor process of the LogicApp .DESCRIPTION Execute all the tasks that have been defined in the runbook file, and get an ARM template as output Depending on the initial extractor task that you are using, your powershell / az cli session needs to be signed in Your runbook file can contain the tasks available from the module, but also your own custom tasks, that you want to have executed as part of the process .PARAMETER Runbook Path to the PSake valid runbook file that you want to have executed while exporting, sanitizing and converting a LogicApp into a deployable ARM template .PARAMETER SubscriptionId Id of the subscription that you want to work against, your current powershell / az cli session either needs to be "connected" to the subscription or at least have permissions to work against the subscription .PARAMETER ResourceGroup Name of the resource group that you want to work against, your current powershell / az cli session needs to have permissions to work against the resource group .PARAMETER Name Name of the logic app, that you want to work against .PARAMETER Task List of task that you want to have executed, based on the runbook file that you pass This allows you to only run a small subset of all the tasks that you have defined inside your runbook Helpful when troubleshooting and trying to identify the best execution order of all the tasks .PARAMETER WorkPath Path to were the tasks will persist their outputs Each task will save a file into a unique folder, containing the formatted output from its operation You could risk that secrets or credentials are being stored on your disk, if they in some way are stored as clear text inside the logic app The default valus is the current users TempPath, where it creates a "\PsLogicAppExtractor\GUID\" directory for each invoke .PARAMETER OutputPath Path to were the ARM template file will be persisted The path has to be a directory The file will be named as the Logic App is named .PARAMETER KeepFiles Instruct the cmdlet to keep all the files, across all tasks This enables troubleshooting and comparison of input vs output, per task, as each task has an input file and the result of the work persisted in the same directory .PARAMETER Tools Instruct the cmdlet which tool to use Options are: AzCli (azure cli) Az.Powershell (Az.Accounts+ PowerShell native modules) .EXAMPLE PS C:\> Invoke-PsLaExtractor -Runbook "C:\temp\LogicApp.ExportOnly.psakefile.ps1" -ResourceGroup "TestRg" -Name TestLogicApp Invokes the different tasks inside the runbook file, to export the TestLogicApp as an ARM template The file needs to be a valid PSake runbook file .EXAMPLE PS C:\> Invoke-PsLaExtractor -Runbook "C:\temp\LogicApp.ExportOnly.psakefile.ps1" -SubscriptionId "f5608f3d-ab28-49d8-9b4e-1b1f812d12e0" -ResourceGroup "TestRg" -Name "TestLogicApp" Invokes the different tasks inside the runbook file, to export the TestLogicApp as an ARM template The file needs to be a valid PSake runbook file .EXAMPLE PS C:\> Invoke-PsLaExtractor -Runbook "C:\temp\LogicApp.ExportOnly.psakefile.ps1" Invokes the different tasks inside the runbook file, to export the TestLogicApp as an ARM template The file needs to be a valid PSake runbook file The runbook file needs to have populated the Properties object, with the minimum: ResourceGroup and SubscriptionId .EXAMPLE PS C:\> Invoke-PsLaExtractor -Runbook "C:\temp\LogicApp.ExportOnly.psakefile.ps1" -ResourceGroup "TestRg" -Name TestLogicApp -WorkPath "C:\temp\work_directory" Invokes the different tasks inside the runbook file, to export the TestLogicApp as an ARM template The file needs to be a valid PSake runbook file Will output all tasks files into the "C:\temp\work_directory" location .EXAMPLE PS C:\> Invoke-PsLaExtractor -Runbook "C:\temp\LogicApp.ExportOnly.psakefile.ps1" -ResourceGroup "TestRg" -Name TestLogicApp -KeepFiles Invokes the different tasks inside the runbook file, to export the TestLogicApp as an ARM template The file needs to be a valid PSake runbook file All files that the different tasks has created, are keept, for the user to analyze them .NOTES Author: Mötz Jensen (@Splaxi) #> function Invoke-PsLaExtractor { [CmdletBinding(DefaultParameterSetName = "PreppedFile")] param ( [Alias('File')] [Parameter(Mandatory = $true, ParameterSetName = "NameOnly")] [Parameter(Mandatory = $true, ParameterSetName = "PreppedFile")] [Parameter(Mandatory = $true, ParameterSetName = "ResourceGroup")] [Parameter(Mandatory = $true, ParameterSetName = "Subscription")] [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')] [string] $Runbook, [Parameter(Mandatory = $true, ParameterSetName = "Subscription")] [string] $SubscriptionId, [Parameter(Mandatory = $true, ParameterSetName = "ResourceGroup")] [Parameter(Mandatory = $true, ParameterSetName = "Subscription")] [string] $ResourceGroup, [Parameter(Mandatory = $true, ParameterSetName = "NameOnly")] [Parameter(Mandatory = $true, ParameterSetName = "ResourceGroup")] [Parameter(Mandatory = $true, ParameterSetName = "Subscription")] [string] $Name, [string[]] $Task, [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')] [string] $WorkPath = "$([System.IO.Path]::GetTempPath())PsLogicAppExtractor\$([System.Guid]::NewGuid().Guid)", [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')] [string] $OutputPath, [switch] $KeepFiles, [ValidateSet('AzCli', 'Az.Powershell')] [string] $Tools = 'Az.Powershell' ) if (-not ($WorkPath -like "*$([System.IO.Path]::GetTempPath())*")) { if ($WorkPath -NotMatch '(?im)[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?') { $WorkPath = "$WorkPath\$([System.Guid]::NewGuid().Guid)" } } #The task counter needs to be reset prior running Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskCounter -Value 0 Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskInputNext -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskOutputFile -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskPath -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.Name -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.Tools -Value "" #Make sure the work path is created and available New-Item -Path $WorkPath -ItemType Directory -Force -ErrorAction Ignore > $null $parms = @{} $parms.buildFile = $Runbook $parms.nologo = $true if ($Task) { $parms.taskList = $Task } Set-PSFConfig -FullName PsLogicAppExtractor.Execution.WorkPath -Value $WorkPath Set-PSFConfig -FullName PsLogicAppExtractor.Execution.Tools -Value $Tools $props = @{} if ($SubscriptionId) { $props.SubscriptionId = $SubscriptionId } if ($ResourceGroup) { $props.ResourceGroup = $ResourceGroup } if ($Name) { $props.Name = $Name Set-PSFConfig -FullName PsLogicAppExtractor.Execution.Name -Value $Name } if ($PsCmdlet.ParameterSetName -eq "PreppedFile") { if ((Get-Content -Path $Runbook -raw) -match '\$Name\W*=\W*"(.*)"') { Set-PSFConfig -FullName PsLogicAppExtractor.Execution.Name -Value $Matches[1] } } $res = Invoke-psake @parms -properties $props -ErrorVariable errorsFound if ($errorsFound) { throw $res } $resPath = Get-ExtractOutput -Path $WorkPath if ($OutputPath) { $resPath = Copy-Item -Path $resPath -Destination "$OutputPath" -PassThru -Force | Select-Object -ExpandProperty FullName } $resPath if (-not $KeepFiles) { Get-ChildItem -Path $WorkPath -File -Recurse | Where-Object { $_.FullName -ne $resPath } | ForEach-Object { Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue -Confirm:$false -Recurse } Get-ChildItem -Path $WorkPath -Directory | Where-Object { $_.FullName -ne $(Split-Path -Path $resPath -Parent) } | ForEach-Object { Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue -Confirm:$false -Recurse } } } <# .SYNOPSIS Create valid runbook file based on the task files in the directory .DESCRIPTION Helps you build a valid runbook file, based on all the individual task files (ps1) that are located in the directory This makes it easy to get a starting point for a new runbook file, based on the tasks you have persisted as individual files Especially helpful for custom tasks that might be stored in a central repository The task files has to valid PSake tasks saved as ps1 files .PARAMETER Path Path to the directory where there are valid PSake tasks saved as ps1 files The default value is set for the internal directory where all the generic tasks that are part of the module is located .PARAMETER SubscriptionId Id of the subscription that you want to work against At runtime / execution of Invoke-PsLaExtractor - your current powershell / az cli session either needs to be "connected" to the subscription or at least have permissions to work against the subscription Useful when you know upfront what you want to work against, as you don't need to pass the parameter into the Invoke-PsLaExtractor .PARAMETER ResourceGroup Name of the resource group that you want to work against At runtime / execution of Invoke-PsLaExtractor - your current powershell / az cli session needs to have permissions to work against the resource group Useful when you know upfront what you want to work against, as you don't need to pass the parameter into the Invoke-PsLaExtractor .PARAMETER Name Name of the logic app, that you want to work against At runtime / execution of Invoke-PsLaExtractor - your current powershell / az cli session needs to have permissions to work against the logic app Useful when you know upfront what you want to work against, as you don't need to pass the parameter into the Invoke-PsLaExtractor .PARAMETER OutputPath Path to were the runbook file will be persisted The path has to be a directory The runbook file will be named: PsLaExtractor.default.psakefile.ps1 .PARAMETER IncludePrefixSuffix Instruct the cmdlet to add the different prefix and suffix options, with the default values that comes with the module This make it easier to make the runbook file work across different environments, without having to worry about prepping different prefix and suffix value prior .EXAMPLE PS C:\> New-PsLaRunbookByPath Creates a valid runbook file, based on the bare minimum and with sane default values Reads all the internal / generic tasks that are part of the module and implements a valid default path for the includes .EXAMPLE PS C:\> New-PsLaRunbookByPath -Path c:\temp\tasks Creates a valid runbook file, based on the bare minimum and with sane default values Reads all ps1 files located in c:\temp\tasks Great to use when you have lots of custom tasks in a directory / repository, and want a good runbook file as starting point .EXAMPLE PS C:\> New-PsLaRunbookByPath -SubscriptionId "f5608f3d-ab28-49d8-9b4e-1b1f812d12e0" -ResourceGroup "TestRg" Creates a valid runbook file, based on the bare minimum and with sane default values Reads all the internal / generic tasks that are part of the module and implements a valid default path for the includes Prepares the Properties object with SubscriptionId and ResourceGroup Useful if you have multiple logic apps in the same resource group and you want them extracted using the same runbook file .EXAMPLE PS C:\> New-PsLaRunbookByPath -SubscriptionId "f5608f3d-ab28-49d8-9b4e-1b1f812d12e0" -ResourceGroup "TestRg" -Name "TestLogicApp" Creates a valid runbook file, based on the bare minimum and with sane default values Reads all the internal / generic tasks that are part of the module and implements a valid default path for the includes Prepares the Properties object with SubscriptionId and ResourceGroup and Name Useful if you want to have a ready to run runbook file, that makes it simple to run the command again and again Great for iterative work, where you make lots of small changes in the logic app and want to see how the changes affect your ARM template .EXAMPLE PS C:\> New-PsLaRunbookByPath -OutputPath c:\temp\PsLaRunbooks Creates a valid runbook file, based on the bare minimum and with sane default values Reads all the internal / generic tasks that are part of the module and implements a valid default path for the includes Outputs the build to c:\temp\PsLaRunbooks The runbook file is default named: PsLaExtractor.default.psakefile.ps1 .EXAMPLE PS C:\> New-PsLaRunbookByPath -IncludePrefixSuffix Creates a valid runbook file, based on the bare minimum and with sane default values Reads all the internal / generic tasks that are part of the module and implements a valid default path for the includes The Properties object inside the runbook file, will be pre-populated with the default prefix and suffix values from the module This make it easier to make the runbook file work across different environments, without having to worry about prepping different prefix and suffix value prior .NOTES Author: Mötz Jensen (@Splaxi) #> function New-PsLaRunbookByPath { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')] [string] $Path = $(Get-PSFConfigValue -FullName PsLogicAppExtractor.ModulePath.Tasks), [string] $SubscriptionId, [string] $ResourceGroup, [string] $Name, [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')] [string] $OutputPath, [switch] $IncludePrefixSuffix ) $files = Get-ChildItem -Path "$Path\*.ps1" $res = New-Object System.Collections.Generic.List[System.Object] $res.AddRange($(Get-BuildHeader -SubscriptionId $SubscriptionId -ResourceGroup $ResourceGroup -Name $Name -IncludePrefixSuffix:$IncludePrefixSuffix)) if ($Path -ne $(Get-PSFConfigValue -FullName PsLogicAppExtractor.ModulePath.Tasks)) { $res.Add("# Path to where the custom task files are located") $res.Add("`$pathTasksCustom = `"$Path`"") } $res.Add("# Array to hold all tasks for the default task") $res.Add("`$listTasks = @()") $res.Add("") $includes = New-Object System.Collections.Generic.List[System.Object] $list = New-Object System.Collections.Generic.List[System.Object] $psake.context = New-Object System.Collections.Stack $psake.context.push( @{ "tasks" = @{} "aliases" = @{} } ) foreach ($item in $files) { $psake.context = New-Object System.Collections.Stack $psake.context.push( @{ "tasks" = @{} "aliases" = @{} } ) . $item.FullName foreach ($task in $psake.context.tasks) { foreach ($value in $task.Values) { $list.Add("`$listTasks += `"$($value.Name)`"") } } } $psake.context = New-Object System.Collections.Stack #TODO! Make sure that we actually need this - other places it was enough with the $psake.context = New-Object System.Collections.Stack # $psake.context.push( # @{ # "buildSetupScriptBlock" = {} # "buildTearDownScriptBlock" = {} # "taskSetupScriptBlock" = {} # "taskTearDownScriptBlock" = {} # "executedTasks" = new-object System.Collections.Stack # "callStack" = new-object System.Collections.Stack # "originalEnvPath" = $env:PATH # "originalDirectory" = get-location # "originalErrorActionPreference" = $global:ErrorActionPreference # "tasks" = @{} # "aliases" = @{} # "properties" = new-object System.Collections.Stack # "includes" = new-object System.Collections.Queue # } # ) $res.Add("# All tasks that needs to be include based on their path") $res.AddRange($includes) $res.Add("") $res.Add("# Building the list of tasks for the default task") $res.AddRange($list) $res.Add("") $res.Add("# Default tasks, the via the dependencies will run all tasks") $res.Add("Task -Name `"default`" -Depends `$listTasks") if ($OutputPath) { New-Item -Path $OutputPath -ItemType Directory -Force -ErrorAction Ignore > $null $path = Join-Path -Path $OutputPath -ChildPath "PsLaExtractor.default.psakefile.ps1" $encoding = New-Object System.Text.UTF8Encoding($true) [System.IO.File]::WriteAllLines($path, $($res.ToArray() -join "`r`n"), $encoding) Get-Item -Path $path | Select-Object -ExpandProperty FullName } else { $res.ToArray() -join "`r`n" } } <# .SYNOPSIS Create valid runbook file based on the task passed as inputs .DESCRIPTION Helps you build a valid runbook file, based on all the individual tasks that are passed as inputs This makes it easy to get a starting point for a new runbook file, based on the tasks you have build in your array and then passes into the cmdlet Tasks are expected to be the ones that are part of the module .PARAMETER Task Names of the tasks that you want to be part of your runbook file Supports array of task names Names of the different tasks are expected to be the ones that are part of the module .PARAMETER SubscriptionId Id of the subscription that you want to work against At runtime / execution of Invoke-PsLaExtractor - your current powershell / az cli session either needs to be "connected" to the subscription or at least have permissions to work against the subscription Useful when you know upfront what you want to work against, as you don't need to pass the parameter into the Invoke-PsLaExtractor .PARAMETER ResourceGroup Name of the resource group that you want to work against At runtime / execution of Invoke-PsLaExtractor - your current powershell / az cli session needs to have permissions to work against the resource group Useful when you know upfront what you want to work against, as you don't need to pass the parameter into the Invoke-PsLaExtractor .PARAMETER Name Name of the logic app, that you want to work against At runtime / execution of Invoke-PsLaExtractor - your current powershell / az cli session needs to have permissions to work against the logic app Useful when you know upfront what you want to work against, as you don't need to pass the parameter into the Invoke-PsLaExtractor .PARAMETER OutputPath Path to were the runbook file will be persisted The path has to be a directory The runbook file will be named: PsLaExtractor.default.psakefile.ps1 .PARAMETER IncludePrefixSuffix Instruct the cmdlet to add the different prefix and suffix options, with the default values that comes with the module This make it easier to make the runbook file work across different environments, without having to worry about prepping different prefix and suffix value prior .EXAMPLE PS C:\> New-PsLaRunbookByTask -Task "Export-LogicApp.AzCli","ConvertTo-Raw","ConvertTo-Arm" Creates a valid runbook file, based on the bare minimum and with sane default values Will write the include and taskList in the mentioned order of the tasks .EXAMPLE PS C:\> New-PsLaRunbookByTask -Task "Export-LogicApp.AzCli","ConvertTo-Raw","ConvertTo-Arm" -SubscriptionId "f5608f3d-ab28-49d8-9b4e-1b1f812d12e0" -ResourceGroup "TestRg" Creates a valid runbook file, based on the bare minimum and with sane default values Will write the include and taskList in the mentioned order of the tasks Prepares the Properties object with SubscriptionId and ResourceGroup Useful if you have multiple logic apps in the same resource group and you want them extracted using the same runbook file .EXAMPLE PS C:\> New-PsLaRunbookByTask -Task "Export-LogicApp.AzCli","ConvertTo-Raw","ConvertTo-Arm" -SubscriptionId "f5608f3d-ab28-49d8-9b4e-1b1f812d12e0" -ResourceGroup "TestRg" -Name "TestLogicApp" Creates a valid runbook file, based on the bare minimum and with sane default values Will write the include and taskList in the mentioned order of the tasks Prepares the Properties object with SubscriptionId and ResourceGroup and Name Useful if you want to have a ready to run runbook file, that makes it simple to run the command again and again Great for iterative work, where you make lots of small changes in the logic app and want to see how the changes affect your ARM template .EXAMPLE PS C:\> New-PsLaRunbookByTask -Task "Export-LogicApp.AzCli","ConvertTo-Raw","ConvertTo-Arm" -OutputPath c:\temp\PsLaRunbooks Creates a valid runbook file, based on the bare minimum and with sane default values Will write the include and taskList in the mentioned order of the tasks Outputs the build to c:\temp\PsLaRunbooks The runbook file is default named: PsLaExtractor.default.psakefile.ps1 .EXAMPLE PS C:\> New-PsLaRunbookByTask -Task "Export-LogicApp.AzCli","ConvertTo-Raw","ConvertTo-Arm" -IncludePrefixSuffix Creates a valid runbook file, based on the bare minimum and with sane default values Will write the include and taskList in the mentioned order of the tasks The Properties object inside the runbook file, will be pre-populated with the default prefix and suffix values from the module This make it easier to make the runbook file work across different environments, without having to worry about prepping different prefix and suffix value prior .NOTES Author: Mötz Jensen (@Splaxi) #> function New-PsLaRunbookByTask { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string[]] $Task, [string] $SubscriptionId, [string] $ResourceGroup, [string] $Name, [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')] [string] $OutputPath, [switch] $IncludePrefixSuffix ) $res = New-Object System.Collections.Generic.List[System.Object] $res.AddRange($(Get-BuildHeader -SubscriptionId $SubscriptionId -ResourceGroup $ResourceGroup -Name $Name -IncludePrefixSuffix:$IncludePrefixSuffix)) $res.Add("# Array to hold all tasks for the default task") $res.Add('$listTasks = @()') $res.Add('') $list = New-Object System.Collections.Generic.List[System.Object] foreach ($item in $Task) { $list.Add("`$listTasks += `"$item`"") } $res.AddRange($list) $res.Add("") $res.Add("# Default tasks, the via the dependencies will run all tasks") $res.Add('Task -Name "default" -Depends $listTasks') if ($OutputPath) { New-Item -Path $OutputPath -ItemType Directory -Force -ErrorAction Ignore > $null $path = Join-Path -Path $OutputPath -ChildPath "PsLaExtractor.default.psakefile.ps1" $encoding = New-Object System.Text.UTF8Encoding($true) [System.IO.File]::WriteAllLines($path, $($res.ToArray() -join "`r`n"), $encoding) Get-Item -Path $path | Select-Object -ExpandProperty FullName } else { $res.ToArray() -join "`r`n" } } <# .SYNOPSIS Output the tasks result to a file .DESCRIPTION Persists the tasks output into a file, either the raw content or the object Sets the $Script:FilePath = $Path, to ensure the next tasks can pick up the file and continue its work Notes: It is considered as an internal function, and should not be used directly. .PARAMETER Path Path to where the tasks wants the ouput to be persisted .PARAMETER Content Raw string that should be written to the desired path .PARAMETER InputObject The object that should be written to the desired path Will be converted to a json string, usign the ConvertTo-Json Important note: If you need the InputObject to be written with a specific structure, the object has to be of the expected type before being passed into the cmdlet A simple cast can ensure this to work as intended .EXAMPLE PS C:\> Out-TaskFile -Path "C:\temp\work_directory\1_Export-LogicApp.AzCli" -InputObject $([ArmTemplate]$armObj) Outputs the armObj variable to the path: "C:\temp\work_directory\1_Export-LogicApp.AzCli" The armObj is casted to the [ArmTemplate] type, to ensure it is persisted as the expected json structure .EXAMPLE PS C:\> Out-TaskFile -Path "C:\temp\work_directory\1_Export-LogicApp.AzCli" -Content '{"Test":"Test"}' Outputs the content string: '{"Test":"Test"}' to the path: "C:\temp\work_directory\1_Export-LogicApp.AzCli" .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Out-TaskFile { [CmdletBinding(DefaultParameterSetName = "InputObject")] param ( [string] $Path = $(Get-PSFConfigValue -FullName PsLogicAppExtractor.Execution.TaskOutputFile), [Parameter(Mandatory = $true, ParameterSetName = "Content")] [string] $Content, [Parameter(Mandatory = $true, ParameterSetName = "InputObject")] [object] $InputObject ) if ($InputObject) { $Content = $InputObject | ConvertTo-Json -Depth 100 } $encoding = New-Object System.Text.UTF8Encoding($true) [System.IO.File]::WriteAllLines($Path, $Content, $encoding) if ($(Get-PSFConfigValue -FullName PsLogicAppExtractor.Execution.TaskInputNext)) { $taskPath = Get-PSFConfigValue -FullName PsLogicAppExtractor.Execution.TaskPath Copy-Item -Path $(Get-PSFConfigValue -FullName PsLogicAppExtractor.Execution.TaskInputNext) -Destination "$taskPath\Input.json" } Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskInputNext -Value $Path } <# .SYNOPSIS Output the tasks result to a file, as an ARM template .DESCRIPTION Persists the tasks output into a file, as an ARM template Notes: It is considered as an internal function, and should not be used directly. .PARAMETER InputObject The object that should be written to the desired path Will be converted to a json string, usign the ConvertTo-Json .EXAMPLE PS C:\> Out-TaskFileArm -InputObject $armObj Outputs the armObj variable The armObj is casted to the [ArmTemplate] type, to ensure it is persisted as the expected json structure .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Out-TaskFileArm { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $InputObject ) Out-TaskFile -InputObject $([ArmTemplate]$InputObject) } <# .SYNOPSIS Output the tasks result to a file, as a LogicApp json structure .DESCRIPTION Persists the tasks output into a file, as a LogicApp json structure Notes: It is considered as an internal function, and should not be used directly. .PARAMETER InputObject The object that should be written to the desired path Will be converted to a json string, usign the ConvertTo-Json .EXAMPLE PS C:\> Out-TaskFileLogicApp -InputObject $lgObj Outputs the armObj variable The armObj is casted to the [ArmTemplate] type, to ensure it is persisted as the expected json structure .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Out-TaskFileLogicApp { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $InputObject ) Out-TaskFile -InputObject $([LogicApp]$InputObject) } <# .SYNOPSIS Remove parameter from the ARM template .DESCRIPTION Removes an ARM template parameter by the name provided Notes: It is considered as an internal function, and should not be used directly. .PARAMETER InputObject The ARM object that you want to work against It has to be a object of the type [ArmTemplate] for it to work properly .PARAMETER Name Name of the parameter that you want to work against If the parameter exists, it will be removed from the InputObject .EXAMPLE PS C:\> Remove-ArmParameter -InputObject $armObj -Name "logicAppName" Removes the logicAppName ARM template parameter .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Remove-ArmParameter { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $InputObject, [Alias('ParameterName')] [Parameter(Mandatory = $true)] [string] $Name ) if ($InputObject.parameters.$Name) { $InputObject.parameters.PsObject.Properties.Remove($Name) } $InputObject } <# .SYNOPSIS Remove variable from the ARM template .DESCRIPTION Removes an ARM template variable by the name provided Notes: It is considered as an internal function, and should not be used directly. .PARAMETER InputObject The ARM object that you want to work against It has to be a object of the type [ArmTemplate] for it to work properly .PARAMETER Name Name of the variable that you want to work against If the variable exists, it will be removed from the InputObject .EXAMPLE PS C:\> Remove-ArmVariable -InputObject $armObj -Name "logicAppName" Removes the logicAppName ARM template variable .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Remove-ArmVariable { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $InputObject, [Alias('VariableName')] [Parameter(Mandatory = $true)] [string] $Name ) if ($InputObject.variables.$Name) { $InputObject.variables.PsObject.Properties.Remove($Name) } $InputObject } <# .SYNOPSIS Remove parm (parameter) from the LogicApp .DESCRIPTION Removes an LogicApp parm (parameter) by the name provided Notes: It is considered as an internal function, and should not be used directly. .PARAMETER InputObject The LogicApp object that you want to work against It has to be a object of the type [LogicApp] for it to work properly .PARAMETER Name Name of the parm (parameter) that you want to work against If the parm (parameter) exists, it will be removed from the InputObject .EXAMPLE PS C:\> Remove-LogicAppParm -InputObject $armObj -Name "TriggerQueue" Removes the TriggerQueue LogicApp parm (parameter) .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Remove-LogicAppParm { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $InputObject, [Alias('ParmName')] [Parameter(Mandatory = $true)] [string] $Name ) if ($InputObject.properties.definition.parameters.$Name) { $InputObject.properties.definition.parameters.PsObject.Properties.Remove($Name) } $InputObject } <# .SYNOPSIS Set the current working directory .DESCRIPTION Sets the current tasks working directory, based on the current PsLaWorkPath and the execution number that task is in the overall execution Outputs the path that has been constructed Notes: It is considered as an internal function, and should not be used directly. .PARAMETER Path Path to the current working directory The value passed in should always be the $PsLaWorkPath, to ensure that things are working .PARAMETER FileName The of the file that you want to be configured for the task Is normally equal to the name of the Logic App .EXAMPLE PS C:\> Set-TaskWorkDirectory Creates a new sub directory under the $PsLaWorkPath location The sub directory is named "$taskCounter`_$TaskName" The output will be: "$Path\$taskCounter`_$TaskName" .NOTES Author: Mötz Jensen (@Splaxi) This is considered as an internal function, and should not be used directly. #> function Set-TaskWorkDirectory { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [string] $Path = $(Get-PSFConfigValue -FullName PsLogicAppExtractor.Execution.WorkPath), [string] $FileName = "$(Get-PSFConfigValue -FullName PsLogicAppExtractor.Execution.Name).json" ) Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskCounter -Value $($(Get-PSFConfigValue -FullName PsLogicAppExtractor.Execution.TaskCounter) + 1) $taskCounter = $(Get-PSFConfigValue -FullName PsLogicAppExtractor.Execution.TaskCounter) $taskName = $($psake.context.Peek().CurrentTaskName) $newPath = "$Path\$taskCounter`_$TaskName" New-Item -Path $newPath -ItemType Directory -Force -ErrorAction Ignore > $null Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskPath -Value $newPath Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskOutputFile -Value "$newPath\$FileName" } <# .SYNOPSIS Split an ARM template into multiple files, based on the resource type .DESCRIPTION You might have a large ARM template, consisting of multiple resource types. This script will create single ARM templates for the specified resource type, and will copy the parameters from the original template .PARAMETER Path Path to the ARM template that you want to work against .PARAMETER OutputPath Path to were the ARM template file will be persisted The path has to be a directory The file will be named as the original ARM template file .PARAMETER TypeFilter Instruct the cmdlet to only process the specified resource type The default value is 'Microsoft.Web/connections' .EXAMPLE PS C:\> Split-ArmTemplate -Path 'C:\temp\template.json' -OutputPath 'C:\temp\output' This will create a new ARM template file for each resource of the type 'Microsoft.Web/connections' in the original ARM template file. The new ARM template files will be persisted in the 'C:\temp\output' directory. .EXAMPLE PS C:\> Split-ArmTemplate -Path 'C:\temp\template.json' This will create a new ARM template file for each resource of the type 'Microsoft.Web/connections' in the original ARM template file. The new ARM template files will be persisted in the same directory as the original ARM template file. .EXAMPLE PS C:\> Split-ArmTemplate -Path 'C:\temp\template.json' -TypeFilter 'Microsoft.Web/sites' This will create a new ARM template file for each resource of the type 'Microsoft.Web/sites' in the original ARM template file. The new ARM template files will be persisted in the same directory as the original ARM template file. .NOTES Author: Mötz Jensen (@Splaxi) #> function Split-PsLaArmTemplate { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding()] param ( [parameter(Mandatory = $true)] [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')] [Alias('File')] [string] $Path, [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')] [string] $OutputPath, [Alias('ResourceType')] [string] $TypeFilter = 'Microsoft.Web/connections' ) if (-not $OutputPath) { $OutputPath = Split-Path -Path $Path -Parent } # #Make sure the output path is created and available New-Item -Path $OutputPath -ItemType Directory -Force -ErrorAction Ignore > $null #The task counter needs to be reset prior running Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskCounter -Value 0 Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskInputNext -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskOutputFile -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskPath -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.Name -Value "" $InputObject = Get-TaskWorkObject -Path $Path # We only want to process the resources that are of the type we are looking for $colFiltered = $InputObject.resources | Where-Object { $_.type -eq $TypeFilter } $baseName = Split-Path -Path $Path -LeafBase for ($i = 0; $i -lt $colFiltered.Count; $i++) { $Destination = Join-Path -Path $OutputPath -ChildPath "$baseName`__$(($i+1).ToString().PadLeft(3, "0")).json" $obj = $colFiltered[$i] # Loading the original ARM template file, but to be used for the local resource $tempArm = Get-TaskWorkObject -Path $Path # The resources is overwritten with the resource we are currently processing $tempArm.resources = @($obj) # We need to do some string manipulation to get the names of the parameters $jsonStr = $obj | ConvertTo-Json -Depth 100 # We need to match all parameters that are used in the resource $parmsToKeep = @(($jsonStr | Select-String "parameters\('(.*?)'\)" -AllMatches).Matches.Groups | Where-Object { $null -eq $_.Groups } | Select-Object -ExpandProperty Value -Unique) # We need to remove all parameters that are not used in the resource $tempArm.Parameters.PsObject.Properties | Where-Object { $parmsToKeep -notcontains $_.Name } | Select-Object -ExpandProperty Name | ForEach-Object { $tempArm = Remove-ArmParameter -InputObject $tempArm -Name $_ } Out-TaskFile -Path $Destination -InputObject $tempArm } } <# .SYNOPSIS Update parameter file from another file. .DESCRIPTION Update parameter file values from another file. The source file can be an ARM template file or a parameter file. .PARAMETER Source The path to the source file to be used as the changes you want to apply. .PARAMETER Destination The path to the destination parameter file to be updated. .PARAMETER KeepDestinationParameters If you want to keep the parameters in the destination file that are not in the source file, set this switch. .PARAMETER KeepValues If you want to keep the values of the parameters in the destination file, set this switch. Useful when you want to align parameters across "environments", but don't want to change the values of the parameters. .PARAMETER ExcludeSourceParameters If you want to exclude parameters from the source file from the update, set this switch. .EXAMPLE PS C:\> Update-PsLaArmParameterFile -Source C:\Temp\Source.json -Destination C:\Temp\Destination.json This will update the parameter file C:\Temp\Destination.json with the values from the parameter file C:\Temp\Source.json. It will only update the parameter values that are present in the destination file. This example illustrates how to update a parameter file from an ARM template file. .EXAMPLE PS C:\> Update-PsLaArmParameterFile -Source C:\Temp\Source.parameters.json -Destination C:\Temp\Destination.json This will update the parameter file C:\Temp\Destination.json with the values from the parameter file C:\Temp\Source.parameters.json. It will only update the parameter values that are present in the destination file. This example illustrates how to update a parameter file from a parameter file. .EXAMPLE PS C:\> Update-PsLaArmParameterFile -Source C:\Temp\Source.parameters.json -Destination C:\Temp\Destination.json -KeepUnusedParameters This will update the parameter file C:\Temp\Destination.json with the values from the parameter file C:\Temp\Source.parameters.json. It will only update the parameter values that are present in the destination file. It will keep the parameters in the destination file that are not in the source file. This example illustrates how to update a parameter file from a parameter file. .EXAMPLE PS C:\> Update-PsLaArmParameterFile -Source C:\Temp\Source.parameters.json -Destination C:\Temp\Destination.json KeepValues This will update the parameter file C:\Temp\Destination.json with the values from the parameter file C:\Temp\Source.parameters.json. It will only update the parameter values that are present in the destination file. It will keep the values of the parameters in the destination file. This example illustrates how to update a parameter file from a parameter file. .NOTES Author: Mötz Jensen (@Splaxi) #> function Update-PsLaArmParameterFile { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [parameter(Mandatory = $true, ValueFromPipeline = $true)] [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')] [string] $Source, [parameter(Mandatory = $true)] [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')] [string] $Destination, [Alias('KeepUnusedParameters')] [switch] $KeepDestinationParameters, [switch] $KeepValues, [string[]] $ExcludeSourceParameters ) process { #The task counter needs to be reset prior running Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskCounter -Value 0 Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskInputNext -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskOutputFile -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskPath -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.Name -Value "" $armObjSource = Get-TaskWorkObject -Path $Source $armObjDestination = Get-TaskWorkObject -Path $Destination foreach ($item in $armObjDestination.parameters.PsObject.Properties) { $valueObj = [ordered]@{} if (-not ($armObjSource.parameters."$($item.Name)") -and (-not $KeepDestinationParameters)) { # Should we use the Remove-ArmParameter function here? $armObjDestination.parameters.PsObject.Properties.Remove($item.Name) continue } if ($KeepValues) { continue } if ($armObjSource.parameters."$($item.Name)".DefaultValue) { $valueObj.value = $armObjSource.parameters."$($item.Name)".DefaultValue } else { $valueObj.value = $armObjSource.parameters."$($item.Name)".Value } $armObjDestination.parameters."$($item.Name)" = $valueObj } foreach ($item in $armObjSource.parameters.PsObject.Properties) { $valueObj = [ordered]@{} if($item.Name -in $ExcludeSourceParameters) { continue } if (-not ($armObjDestination.parameters."$($item.Name)")) { if ($armObjSource.parameters."$($item.Name)".DefaultValue) { $valueObj.value = $armObjSource.parameters."$($item.Name)".DefaultValue } else { $valueObj.value = $armObjSource.parameters."$($item.Name)".Value } # Should we use the Add-ArmParameter function here? $armObjDestination.parameters | Add-Member -MemberType NoteProperty -Name "$($item.Name)" -Value $valueObj } } Out-TaskFile -Path $Destination -InputObject $armObjDestination } } <# .SYNOPSIS Update ARM template from another ARM template. .DESCRIPTION Update an ARM template, with the content of another ARM template. You can update the entire ARM template, or just a part of it. Supports the following options: * Full * SkipResources * SkipParameters .PARAMETER Source The path to the source ARM template to be used as the changes you want to apply. .PARAMETER Destination The path to the destination ARM template to be updated. .PARAMETER SkipParameters If true, then the parameters will not be updated. .PARAMETER SkipResources If true, then the resources will not be updated. .PARAMETER RemoveSource If true, then the source ARM template will be removed after it has been used. .EXAMPLE PS C:\> Update-PsLaArmTemplate -Source C:\Temp\Source.json -Destination C:\Temp\Destination.json This will update the ARM template in C:\Temp\Destination.json from the ARM template in C:\Temp\Source.json. It will overwrite the entire ARM template, but will not remove the source ARM template. .EXAMPLE PS C:\> Update-PsLaArmTemplate -Source C:\Temp\Source.json -Destination C:\Temp\Destination.json -SkipParameters This will update the ARM template in C:\Temp\Destination.json from the ARM template in C:\Temp\Source.json. It will overwrite the entire ARM template, but leave the parameters alone. .EXAMPLE PS C:\> Update-PsLaArmTemplate -Source C:\Temp\Source.json -Destination C:\Temp\Destination.json -SkipResources This will update the ARM template in C:\Temp\Destination.json from the ARM template in C:\Temp\Source.json. It will overwrite the entire ARM template, but leave the resources alone. .EXAMPLE PS C:\> Update-PsLaArmTemplate -Source C:\Temp\Source.json -Destination C:\Temp\Destination.json -RemoveSource This will update the ARM template in C:\Temp\Destination.json from the ARM template in C:\Temp\Source.json. It will overwrite the entire ARM template, and remove the source ARM template. .NOTES Author: Mötz Jensen (@Splaxi) #> function Update-PsLaArmTemplate { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [parameter(Mandatory = $true, ValueFromPipeline = $true)] [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')] [string] $Source, [parameter(Mandatory = $true)] [PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')] [string] $Destination, [switch] $SkipParameters, [switch] $SkipResources, [switch] $RemoveSource ) process { #The task counter needs to be reset prior running Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskCounter -Value 0 Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskInputNext -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskOutputFile -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.TaskPath -Value "" Set-PSFConfig -FullName PsLogicAppExtractor.Execution.Name -Value "" $armObjSource = Get-TaskWorkObject -Path $Source $armObjDestination = Get-TaskWorkObject -Path $Destination if (-not $SkipParameters) { $armObjDestination.parameters = $armObjSource.parameters | ConvertTo-Json -Depth 10 | ConvertFrom-Json -Depth 10 } if (-not $SkipResources) { $armObjDestination.resources = @($armObjSource.resources | ConvertTo-Json -Depth 100 | ConvertFrom-Json -Depth 100) } Out-TaskFile -Path $Destination -InputObject $([ArmTemplate]$armObjDestination) if ($RemoveSource) { Remove-Item -Path $Source -Force -Recurse -ErrorAction SilentlyContinue } } } <# This is an example configuration file By default, it is enough to have a single one of them, however if you have enough configuration settings to justify having multiple copies of it, feel totally free to split them into multiple files. #> <# # Example Configuration Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'" #> Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'ModulePath.Base' -Value $script:ModuleRoot -Initialize -Description "The base path for the module, used for the internal functions to be able to provide the full path for internal objects." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'ModulePath.Tasks' -Value "$($script:ModuleRoot)\internal\tasks" -Initialize -Description "The full path for all generic tasks that are part of the module." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'ModulePath.Classes' -Value "$($script:ModuleRoot)\internal\classes" -Initialize -Description "The full path for all the generic classes that are part of the module." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.tag.prefix' -Value "tag_" -Initialize -Description "The default prefix for Tag objects, used as a fallback value for the Format-Name cmdlet." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.tag.suffix' -Value "" -Initialize -Description "The default suffix for Tag objects, used as a fallback value for the Format-Name cmdlet. Be default an empty string" Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.parm.prefix' -Value "parm_" -Initialize -Description "The default prefix for parm (parameter) objects, used as a fallback value for the Format-Name cmdlet." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.parm.suffix' -Value "" -Initialize -Description "The default suffix for parm (parameter) objects, used as a fallback value for the Format-Name cmdlet. Be default an empty string." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.connection.prefix' -Value "connection_" -Initialize -Description "The default prefix for connection objects, used as a fallback value for the Format-Name cmdlet." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.connection.suffix' -Value "_id" -Initialize -Description "The default suffix for connection objects, used as a fallback value for the Format-Name cmdlet." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.trigger.prefix' -Value "trigger_" -Initialize -Description "The default prefix for trigger objects, used as a fallback value for the Format-Name cmdlet." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'prefixsuffix.trigger.suffix' -Value "" -Initialize -Description "The default suffix for trigger objects, used as a fallback value for the Format-Name cmdlet. Be default an empty string." Set-PSFConfig -Module 'PsLogicAppExtractor' -Name 'ModulePath.Queries' -Value "$($script:ModuleRoot)\internal\queries" -Initialize -Description "The full path for all queries used in different cmdlets / functions." <# Stored scriptblocks are available in [PsfValidateScript()] attributes. This makes it easier to centrally provide the same scriptblock multiple times, without having to maintain it in separate locations. It also prevents lengthy validation scriptblocks from making your parameter block hard to read. Set-PSFScriptblock -Name 'PsLogicAppExtractor.ScriptBlockName' -Scriptblock { } #> <# # Example: Register-PSFTeppScriptblock -Name "PsLogicAppExtractor.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' } #> <# # Example: Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name PsLogicAppExtractor.alcohol #> New-PSFLicense -Product 'PsLogicAppExtractor' -Manufacturer 'Motz' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2022-04-04") -Text @" Copyright (c) 2022 Motz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "@ #endregion Load compiled code |