├── .github └── pull_request_template.md ├── ARMHelper ├── ARMHelper.format.ps1xml ├── ARMHelper.psd1 ├── ARMHelper.psm1 ├── Private │ ├── Get-ARMResource.ps1 │ ├── Get-ResourceProperty.ps1 │ └── Test-ARMAzureModule.ps1 └── Public │ ├── Get-ARMDeploymentErrorMessage.ps1 │ ├── Test-ARMDeploymentResource.ps1 │ └── Test-ARMexistingResource.ps1 ├── CHANGELOG.md ├── Devazure-pipeline.yml ├── LICENSE ├── README.md ├── Tests ├── AzureTesting │ ├── Get-ARMDEploymentErrorMessage.tests.ps1 │ ├── StorageAccountBroken │ │ ├── azuredeploy.json │ │ └── azuredeploy.parameters.json │ ├── StorageAccountFixed │ │ ├── azuredeploy.json │ │ └── azuredeploy.parameters.json │ ├── StorageAccountGE │ │ ├── azuredeploy.json │ │ └── azuredeploy.parameters.json │ ├── Test-ARMDeploymentResource.tests.ps1 │ ├── VirtualMachine │ │ ├── azuredeploy.json │ │ └── azuredeploy.parameters.json │ └── pipelinetest.ps1 ├── Basic_Module.tests.ps1 ├── Get-ARMDeploymentErrorMessage.tests.ps1 ├── GetHelp.tests.ps1 ├── MockObjects │ ├── ExistingResources.json │ ├── ExistingResourcesDeleted.json │ ├── ExistingResourcesdiffRG.json │ ├── Logoutput.json │ ├── NestedResult.json │ ├── Result.json │ ├── ResultComplete.json │ ├── ResultCompleteaz.json │ ├── Resultaz.json │ └── azuredeploy.json ├── PSScriptAnalyzer.ps1 ├── Test-ARMAzureModule.tests.ps1 ├── Test-ARMDeploymentResource.tests.ps1 └── Test-ARMExistingResource.tests.ps1 ├── azure-pipelines.yml ├── azure-pipelinesazuretemplate.yml ├── azure-pipelinestemplate.yml └── docs ├── Get-ARMDeploymentErrorMessage.md ├── Test-ARMDeploymentResource.md ├── Test-ARMExistingResource.md └── en-US └── ArmHelper-help.xml /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | description here 4 | 5 | ### Checklist 6 | 7 | - [ ] Issue referenced with # 8 | - [ ] Build for push succeeded 9 | - [ ] Changelog updated 10 | - [ ] Version in psd updated 11 | - [ ] Docs updated 12 | -------------------------------------------------------------------------------- /ARMHelper/ARMHelper.format.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Default 6 | 7 | ArmHelper.Default 8 | 9 | 10 | TypeShort 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Resource 19 | 20 | 21 | Name 22 | 23 | 24 | Type 25 | 26 | 27 | Location 28 | 29 | 30 | Mode 31 | 32 | 33 | Id 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ExistingResource 42 | 43 | ArmHelper.ExistingResource 44 | 45 | 46 | 47 | 48 | 50 49 | 50 | 51 | 50 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | type 62 | 63 | 64 | name 65 | 66 | 67 | ResourcegroupName 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /ARMHelper/ARMHelper.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'Psado' 3 | # 4 | # Generated by: Barbara Forbes 5 | # 6 | # Generated on: 11/02/2019 7 | # 8 | @{ 9 | # Script module or binary module file associated with this manifest. 10 | RootModule = 'ARMHelper.psm1' 11 | 12 | # Version number of this module. 13 | ModuleVersion = '0.6.3' 14 | 15 | # Supported PSEditions 16 | # CompatiblePSEditions = @() 17 | 18 | # ID used to uniquely identify this module 19 | GUID = '037c5c96-e20f-409c-8e42-5963294bb2b3' 20 | 21 | # Author of this module 22 | Author = 'Barbara Forbes' 23 | 24 | # Company or vendor of this module 25 | CompanyName = '4bes.nl' 26 | 27 | # Copyright statement for this module 28 | Copyright = '(c) Barbara Forbes. All rights reserved.' 29 | 30 | # Description of the functionality provided by this module 31 | Description = 'Functions to help with the deployment of ARM Templates.' 32 | 33 | # Minimum version of the PowerShell engine required by this module 34 | # PowerShellVersion = '' 35 | 36 | # Name of the PowerShell host required by this module 37 | # PowerShellHostName = '' 38 | 39 | # Minimum version of the PowerShell host required by this module 40 | # PowerShellHostVersion = '' 41 | 42 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 43 | # DotNetFrameworkVersion = '' 44 | 45 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 46 | # CLRVersion = '' 47 | 48 | # Processor architecture (None, X86, Amd64) required by this module 49 | # ProcessorArchitecture = '' 50 | 51 | # Modules that must be imported into the global environment prior to importing this module 52 | # RequiredModules = @() 53 | 54 | # Assemblies that must be loaded prior to importing this module 55 | # RequiredAssemblies = @() 56 | 57 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 58 | # ScriptsToProcess = @() 59 | 60 | # Type files (.ps1xml) to be loaded when importing this module 61 | # TypesToProcess = @() 62 | 63 | # Format files (.ps1xml) to be loaded when importing this module 64 | FormatsToProcess = @('ARMHelper.format.ps1xml') 65 | 66 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 67 | # NestedModules = @() 68 | 69 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 70 | FunctionsToExport = @( 71 | 'Test-ARMDeploymentResource' 72 | 'Get-ARMDeploymentErrorMessage' 73 | 'Test-ARMexistingResource' 74 | ) 75 | 76 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 77 | CmdletsToExport = @() 78 | 79 | # Variables to export from this module 80 | VariablesToExport = @() 81 | 82 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 83 | AliasesToExport = @() 84 | 85 | # DSC resources to export from this module 86 | # DscResourcesToExport = @() 87 | 88 | # List of all modules packaged with this module 89 | # ModuleList = @() 90 | 91 | # List of all files packaged with this module 92 | # FileList = @() 93 | 94 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 95 | PrivateData = @{ 96 | 97 | PSData = @{ 98 | 99 | # Tags applied to this module. These help with module discovery in online galleries. 100 | Tags = @('Windows','Linux','MacOS','ARM') 101 | 102 | # A URL to the license for this module. 103 | # LicenseUri = '' 104 | 105 | # A URL to the main website for this project. 106 | ProjectUri = 'https://github.com/Ba4bes/ARMHelper' 107 | 108 | # A URL to an icon representing this module. 109 | # IconUri = '' 110 | 111 | # ReleaseNotes of this module 112 | # ReleaseNotes = '' 113 | 114 | } # End of PSData hashtable 115 | 116 | } # End of PrivateData hashtable 117 | 118 | # HelpInfo URI of this module 119 | # HelpInfoURI = '' 120 | 121 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 122 | # DefaultCommandPrefix = '' 123 | 124 | } 125 | -------------------------------------------------------------------------------- /ARMHelper/ARMHelper.psm1: -------------------------------------------------------------------------------- 1 | #Get public and private function definition files. 2 | $Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue ) 3 | $Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue ) 4 | #$Public = @( Get-ChildItem -Path 'C:\Scripts\GIT\Gitlab - Private\DagelijksGehenk\Armado\*.ps1' -ErrorAction SilentlyContinue ) 5 | 6 | 7 | $Scripts = $Private + $Public 8 | 9 | #Dot source the files 10 | Foreach ($import in $Scripts) { 11 | Try { 12 | . $import.fullname 13 | } 14 | Catch { 15 | Write-Error -Message "Failed to import function $($import.fullname): $_" 16 | } 17 | } -------------------------------------------------------------------------------- /ARMHelper/Private/Get-ARMResource.ps1: -------------------------------------------------------------------------------- 1 | 2 | Function Get-ARMResource { 3 | [CmdletBinding(DefaultParameterSetName = "__AllParameterSets")] 4 | Param( 5 | [Parameter( 6 | Position = 1, 7 | Mandatory = $true, 8 | ParameterSetName = "__AllParameterSets" 9 | )] 10 | [ValidateNotNullorEmpty()] 11 | [string] $ResourceGroupName, 12 | 13 | [Parameter( 14 | Position = 2, 15 | Mandatory = $true, 16 | ParameterSetName = "__AllParameterSets" 17 | )] 18 | [ValidateNotNullorEmpty()] 19 | [string] $TemplateFile, 20 | 21 | [Parameter( 22 | ParameterSetName = 'TemplateParameterFile', 23 | Mandatory = $true 24 | )] 25 | [string] $TemplateParameterFile, 26 | 27 | [Parameter( 28 | ParameterSetName = 'TemplateParameterObject', 29 | Mandatory = $true 30 | )] 31 | [hashtable] $TemplateParameterObject, 32 | 33 | [parameter ( 34 | ParameterSetName = "__AllParameterSets", 35 | Mandatory = $false 36 | )] 37 | [ValidateSet("Incremental", "Complete")] 38 | [string] $Mode = "Incremental" 39 | ) 40 | DynamicParam { 41 | if ($TemplateFile) { 42 | #create a new ParameterAttribute Object 43 | $OverRideParameter = New-Object System.Management.Automation.ParameterAttribute 44 | $OverRideParameter.Mandatory = $false 45 | #create an attributecollection object for the attribute we just created. 46 | $AttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute] 47 | $AttributeCollection.Add($OverRideParameter) 48 | $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary 49 | $Parameters = (Get-Content $TemplateFile | ConvertFrom-Json).parameters 50 | $ParameterValues = $parameters | Get-Member -MemberType NoteProperty 51 | ForEach ($Param in $ParameterValues) { 52 | $Name = $Param.Name 53 | $type = ($Parameters.$Name).type 54 | #add our paramater specifying the attribute collection 55 | $ExtraParam = New-Object System.Management.Automation.RuntimeDefinedParameter($Param.Name, ($Type -as [type]), $attributeCollection) 56 | #expose the name of our parameter 57 | 58 | $paramDictionary.Add($Param.Name, $ExtraParam) 59 | } 60 | return $paramDictionary 61 | } 62 | } 63 | process { 64 | #set variables 65 | $Parameters = @{ 66 | ResourceGroupName = $ResourceGroupName 67 | TemplateFile = $TemplateFile 68 | Mode = $Mode 69 | } 70 | if (-not[string]::IsNullOrEmpty($TemplateParameterFile) ) { 71 | $Parameters.Add("TemplateParameterFile", $TemplateParameterFile) 72 | } 73 | if (-not[string]::IsNullOrEmpty($TemplateParameterObject) ) { 74 | $Parameters.Add("TemplateParameterObject", $TemplateParameterObject) 75 | } 76 | $CustomParameters = (Get-Content $TemplateFile | ConvertFrom-Json).parameters 77 | $CustomParameterValues = $Customparameters | Get-Member -MemberType NoteProperty 78 | foreach ($param in $CustomParameterValues) { 79 | $paramname = $param.Name 80 | if (-not[string]::IsNullOrEmpty($PSBoundParameters.$paramname)) { 81 | $Key = $paramname 82 | $Value = $PSBoundParameters.$paramname 83 | $Parameters.Add($Key, $Value) 84 | } 85 | } 86 | $Output = $null 87 | #set debugpreference to continue so the cmdlet runs with more output 88 | $Module = Test-ARMAzureModule 89 | $oldDebugPreference = $DebugPreference 90 | $DebugPreference = "Continue" 91 | 92 | 93 | if ($Module -eq "Az") { 94 | $Output = Test-AzResourceGroupDeployment @parameters 5>&1 -ErrorAction Stop 95 | } 96 | elseif ($Module -eq "AzureRM") { 97 | $Output = Test-AzureRmResourceGroupDeployment @parameters 5>&1 -ErrorAction Stop 98 | 99 | } 100 | else { 101 | Throw "Something went wrong, No AzureRM of AZ module found" 102 | } 103 | #Set DebugPreference back to original setting 104 | $DebugPreference = $oldDebugPreference 105 | if ([string]::IsNullOrEmpty($Output)) { 106 | Throw "Something went wrong, Test-AzureRmResourceGroupDeployment didn't give output" 107 | } 108 | #Grap the specific part of the output that tells you about the deployed Resources 109 | $Response = $Output | Where-Object { $_.Message -like "*http response*" } 110 | #get the jsonpart en convert it to work with it. 111 | $Result = (($Response -split "Body:")[1] | ConvertFrom-Json).Properties 112 | 113 | $Result 114 | } 115 | } -------------------------------------------------------------------------------- /ARMHelper/Private/Get-ResourceProperty.ps1: -------------------------------------------------------------------------------- 1 | 2 | <# 3 | .SYNOPSIS 4 | Returns a HashTable with all properties of an object, including nested properties 5 | 6 | .DESCRIPTION 7 | This function goes through all properties and puts them on one level. 8 | 9 | .PARAMETER Object 10 | Mandatory - The object to list properties of 11 | 12 | .PARAMETER MaxLevels 13 | Specifies how many levels deep to list 14 | 15 | .PARAMETER PathName 16 | Specifies the path name to use as the root. If not specified, all properties will start with "." 17 | 18 | .PARAMETER Level 19 | Specifies which level the function is currently processing. Should not be used manually. 20 | 21 | .EXAMPLE 22 | Get-ResourceProperty -Object $Resource 23 | 24 | .NOTES 25 | This is a modification of a script by KevinD. 26 | http://stackoverflow.com/users/1298933/kevind 27 | Source: https://stackoverflow.com/questions/22388226/powershell-script-delete-first-character-in-output 28 | Modified by: Barbara Forbes 29 | Module: ARMHelper 30 | https://4bes.nl 31 | @Ba4bes 32 | #> 33 | function Get-ResourceProperty { 34 | [CmdletBinding()] 35 | param ( 36 | [Parameter(Position = 1, Mandatory = $true)] 37 | [psobject] $Object, 38 | [Parameter()] 39 | [int] $MaxLevels = 10, 40 | [Parameter()] 41 | [string] $PathName = "", 42 | [Parameter()] 43 | [int] $Level = 0 44 | ) 45 | #Initialize an array to store properties 46 | $Props = @() 47 | if ($Level -eq 0) { 48 | $PropertiesReadable = @{ } 49 | } 50 | $RootProperties = $Object | Get-Member -MemberType NoteProperty 51 | 52 | # Make sure we're not exceeding the MaxLevels 53 | if ($Level -lt $MaxLevels) { 54 | 55 | # Properties of the following types don't need another loop 56 | $TypesToWrite = "System.Boolean", "System.String", "System.Int32", "System.Char" 57 | 58 | #Loop through the root properties 59 | foreach ($RootProperty in $RootProperties) { 60 | 61 | #Base name of property 62 | $Propname = $RootProperty.Name 63 | 64 | #Object to process 65 | $PropertyObject = $($Object.$Propname) 66 | if ($Null -eq $PropertyObject) { 67 | Continue 68 | } 69 | # Get the type, and only recurse into it if it is not one of our excluded types 70 | $Type = ($PropertyObject.GetType()).tostring() 71 | $Array = ($PropertyObject.GetType()).BaseType.tostring() 72 | 73 | # If it's an array, go through each object 74 | if ($Array -eq "System.Array") { 75 | foreach ($PropObject in $PropertyObject) { 76 | $Key = $PropObject.Name 77 | $Value = $PropObject.Value 78 | if ([string]::IsNullOrEmpty($key)) { 79 | continue 80 | } 81 | if ([string]::IsNullOrEmpty($Value)) { 82 | $Members = ($PropObject | get-member -Type NoteProperty | Where-Object { $_.Name -ne "Name" }).Name 83 | $Value = $PropObject.$Members 84 | } 85 | if ($PropertiesReadable.$Key) { 86 | $Path = $PathName.Replace(".properties", "") 87 | $Key = "$Path.$($PropObject.Name)" 88 | } 89 | if ($SecureParameters -contains $Key) { 90 | # This is a bit of a workaround to avoid a plaintext securestring 91 | # The only thing that's better is that this doesn't trigger PSScriptAnalyzer :') 92 | $SecValue = New-Object SecureString 93 | [char[]]($Value) | ForEach-Object { $SecValue.AppendChar($_) } 94 | $Value = $SecValue 95 | } 96 | $PropertiesReadable.add($Key, $Value) 97 | Continue 98 | } 99 | } 100 | #If $TypesToWrite containt the type, write results to hashtable 101 | Elseif (($TypesToWrite.Contains($Type) ) ) { 102 | 103 | $Props += "$PathName.$($RootProperty.Name)" 104 | $Key = $RootProperty.Name 105 | $Value = $PropertyObject 106 | 107 | # Add tags for readability 108 | if ($PathName -like "*Tags*") { 109 | $Key = "Tags: $($RootProperty.Name)" 110 | 111 | } 112 | if ($PropertiesReadable.$Key) { 113 | $Path = $PathName.Replace(".properties", "") 114 | $Key = "$Path.$($RootProperty.Name)" 115 | } 116 | if ($SecureParameters -contains $Key) { 117 | # This is a bit of a workaround to avoid a plaintext securestring 118 | # The only thing that's better is that this doesn't trigger PSScriptAnalyzer :') 119 | $SecValue = New-Object SecureString 120 | [char[]]($Value) | ForEach-Object { $SecValue.AppendChar($_) } 121 | $Value = $SecValue 122 | } 123 | $PropertiesReadable.add($Key, $Value) 124 | } 125 | # If $TypesToWrite does not contain the type, recurse. 126 | Elseif (-not($TypesToWrite.Contains($Type) ) ) { 127 | #Create a new path to the property 128 | $ChildPathName = "$PathName.$Propname" 129 | 130 | # Make sure it's not null, then recurse, incrementing $Level 131 | if ($Null -ne $PropertyObject) { 132 | $Props += Get-ResourceProperty -Object $PropertyObject -PathName $ChildPathName -Level ($Level + 1) -MaxLevels $MaxLevels 133 | } 134 | } 135 | } 136 | } 137 | $PropertiesReadable 138 | } 139 | -------------------------------------------------------------------------------- /ARMHelper/Private/Test-ARMAzureModule.ps1: -------------------------------------------------------------------------------- 1 | Function Test-ARMAzureModule { 2 | 3 | $Module = $null 4 | $AzLoaded = Get-Module -Name Az.* 5 | $AzureRMLoaded = Get-Module -Name AzureRM.* 6 | if (-not[string]::IsNullOrEmpty($AzLoaded)) { 7 | $Module = "Az" 8 | } 9 | elseif (-not[string]::IsNullOrEmpty($AzureRMLoaded)) { 10 | $Module = "AzureRM" 11 | } 12 | Else { 13 | $Az = Get-InstalledModule AZ -ErrorAction SilentlyContinue 14 | $AzureRM = get-installedModule AzureRM -ErrorAction SilentlyContinue 15 | If (-not[string]::IsNullOrEmpty($Az)) { 16 | $Module = "Az" 17 | } 18 | Elseif (-not[string]::IsNullOrEmpty($AzureRM)) { 19 | $Module = "AzureRM" 20 | } 21 | } 22 | Write-Verbose "Az is found" 23 | try { 24 | if ($Module -eq "Az") { 25 | $null = Get-AzContext 26 | } 27 | elseif ($Module -eq "AzureRM") { 28 | $null = Get-AzureRMContext 29 | } 30 | } 31 | Catch { 32 | Throw "No connection with Az has been found. Please Connect." 33 | } 34 | 35 | if ([string]::IsNullOrEmpty($Module)) { 36 | Throw "neither AZ of AzureRM could be loaded" 37 | } 38 | $Module 39 | } -------------------------------------------------------------------------------- /ARMHelper/Public/Get-ARMDeploymentErrorMessage.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Tests an azure deployment for errors, Use the azure Logs if a generic message is given. 4 | 5 | .DESCRIPTION 6 | This function uses Test-AzureRmResourceGroupDeployment or Test-AZResourcegroupDeployment. There is a specific errormessage that's very generic. 7 | If this is the output, the correct errormessage is retrieved from the Azurelog. 8 | 9 | .PARAMETER ResourceGroupName 10 | The resourcegroup where the resources would be deployed to. This resourcegroup needs to exist. 11 | 12 | .PARAMETER TemplateFile 13 | The path to the templatefile 14 | 15 | .PARAMETER TemplateParameterFile 16 | The path to the parameterfile, optional 17 | 18 | .PARAMETER TemplateParameterObject 19 | A Hasbtable with parameters, optional 20 | 21 | .PARAMETER Pipeline 22 | Use this parameter if this script is used in a CICDpipeline. It will make the step fail. 23 | This parameter is replaced by ThrowOnError and will be removed in a later release! 24 | 25 | .PARAMETER ThrowOnError 26 | This Switch will make the cmdlet throw when the deployment is incorrect. This can be useful in a pipeline, it will make the task fail. 27 | 28 | .EXAMPLE 29 | Get-ARMDeploymentErrorMessage -ResourceGroupName ArmTest -TemplateFile .\azuredeploy.json -TemplateParameterFile .\azuredeploy.parameters.json 30 | 31 | -------- 32 | the output is a generic error message. The log is searched for a more clear errormessageGeneral Error. Find info below: 33 | ErrorCode: InvalidDomainNameLabel 34 | Errormessage: The domain name label LABexample is invalid. It must conform to the following regular expression: ^[a-z][a-z0-9-]{1,61}[a-z0-9]$. 35 | 36 | .EXAMPLE 37 | Get-ARMDeploymentErrorMessage Armtesting .\VM01\azuredeploy.json -TemplateParameterObject $Parameters 38 | 39 | -------- 40 | deployment is correct 41 | 42 | .NOTES 43 | Dynamic Parameters like in the orginal Test-AzResourcegroupDeployment-cmdlet are supported 44 | Author: Barbara Forbes 45 | Module: ARMHelper 46 | https://4bes.nl 47 | @Ba4bes 48 | #> 49 | function Get-ARMDeploymentErrorMessage { 50 | [CmdletBinding(DefaultParameterSetName = "__AllParameterSets")] 51 | Param( 52 | [Parameter( 53 | Position = 1, 54 | Mandatory = $true, 55 | ParameterSetName = "__AllParameterSets" 56 | )] 57 | [ValidateNotNullorEmpty()] 58 | [string] $ResourceGroupName, 59 | 60 | [Parameter( 61 | Position = 2, 62 | Mandatory = $true, 63 | ParameterSetName = "__AllParameterSets" 64 | )] 65 | [ValidateNotNullorEmpty()] 66 | [string] $TemplateFile, 67 | 68 | [Parameter( 69 | ParameterSetName = 'TemplateParameterFile', 70 | Mandatory = $true 71 | )] 72 | [string] $TemplateParameterFile, 73 | 74 | [Parameter( 75 | ParameterSetName = 'TemplateParameterObject', 76 | Mandatory = $true 77 | )] 78 | [hashtable] $TemplateParameterObject, 79 | [Parameter( 80 | ParameterSetName = "__AllParameterSets" 81 | )] 82 | [switch] $Pipeline, 83 | 84 | [Parameter( 85 | ParameterSetName = "__AllParameterSets" 86 | )] 87 | [switch] $ThrowOnError 88 | ) 89 | DynamicParam { 90 | if ($TemplateFile) { 91 | #create a new ParameterAttribute Object 92 | $OverRideParameter = New-Object System.Management.Automation.ParameterAttribute 93 | $OverRideParameter.Mandatory = $false 94 | #create an attributecollection object for the attribute we just created. 95 | $AttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute] 96 | $AttributeCollection.Add($OverRideParameter) 97 | $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary 98 | $Parameters = (Get-Content $TemplateFile | ConvertFrom-Json).parameters 99 | $ParameterValues = $parameters | Get-Member -MemberType NoteProperty 100 | ForEach ($Param in $ParameterValues) { 101 | $Name = $Param.Name 102 | $type = ($Parameters.$Name).type 103 | #add our paramater specifying the attribute collection 104 | $ExtraParam = New-Object System.Management.Automation.RuntimeDefinedParameter($Param.Name, ($Type -as [type]), $attributeCollection) 105 | 106 | #expose the name of our parameter 107 | 108 | $paramDictionary.Add($Param.Name, $ExtraParam) 109 | } 110 | return $paramDictionary 111 | } 112 | } 113 | process { 114 | 115 | 116 | if ($Pipeline) { 117 | Write-Warning "This parameter will be removed in the next release. Please use -ThrowOnError as an replacement" 118 | } 119 | 120 | #set variables 121 | $Output = $null 122 | $DetailedError = $null 123 | $Parameters = @{ 124 | ResourceGroupName = $ResourceGroupName 125 | TemplateFile = $TemplateFile 126 | } 127 | if (-not[string]::IsNullOrEmpty($TemplateParameterFile) ) { 128 | $Parameters.Add("TemplateParameterFile", $TemplateParameterFile) 129 | } 130 | if (-not[string]::IsNullOrEmpty($TemplateParameterObject) ) { 131 | $Parameters.Add("TemplateParameterObject", $TemplateParameterObject) 132 | } 133 | 134 | $CustomParameters = (Get-Content $TemplateFile | ConvertFrom-Json).parameters 135 | $CustomParameterValues = $Customparameters | Get-Member -MemberType NoteProperty 136 | foreach ($param in $CustomParameterValues) { 137 | $paramname = $param.Name 138 | if (-not[string]::IsNullOrEmpty($PSBoundParameters.$paramname)) { 139 | $Key = $paramname 140 | $Value = $PSBoundParameters.$paramname 141 | $Parameters.Add($Key, $Value) 142 | } 143 | } 144 | 145 | #Get the AzureModule that's being used 146 | $Module = Test-ARMAzureModule 147 | try { 148 | if ($Module -eq "Az") { 149 | $Output = Test-AzResourceGroupDeployment @parameters 150 | } 151 | elseif ($Module -eq "AzureRM") { 152 | $Output = Test-AzureRmResourceGroupDeployment @parameters 153 | } 154 | else { 155 | Throw "Something went wrong, No AzureRM of AZ module found" 156 | } 157 | } 158 | catch { 159 | throw "Could not test deployment because of following error $_" 160 | } 161 | 162 | #Check for a specific output. This output is a very generic error-message. 163 | #So this script looks for the more clear errormessage in the AzureLogs. 164 | if ($Output.Message -like "*s not valid according to the validation procedure*") { 165 | Write-Output "the output is a generic error message. The log is searched for a more clear errormessage" 166 | #use regex to find the ID of the log 167 | $Regex = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}' 168 | $IDs = $Output.Message | Select-String $Regex -AllMatches 169 | $trackingID = $IDs.Matches.Value | Select-Object -Last 1 170 | $MaxTries = 0 171 | do { 172 | Start-Sleep 30 173 | Write-Output "The log is searched." 174 | 175 | if ($Module -eq "Az") { 176 | $LogContent = (Get-AzLog -CorrelationId $trackingID -WarningAction ignore).Properties.Content 177 | } 178 | elseif ($Module -eq "AzureRM") { 179 | $LogContent = (Get-AzureRmLog -CorrelationId $trackingID -WarningAction ignore).Properties.Content 180 | } 181 | else { 182 | Throw "Something went wrong, No AzureRM of AZ module found" 183 | } 184 | $MaxTries ++ 185 | } while ($null -eq $LogContent -and $maxtries -le 10) 186 | 187 | if ($maxtries -gt 10 ) { 188 | Throw "Can't get Azure Log Entry. Please check the log manually in the portal." 189 | } 190 | if ([string]::IsNullOrEmpty($LogContent)) { 191 | Throw "Something went wrong when collecting the log. Please try again" 192 | } 193 | 194 | $DetailedError = ($LogContent[0].statusMessage) 195 | $TestError = $DetailedError | ConvertFrom-Json 196 | 197 | # The structure for the Log content is inconsistent. This is why a few tricks are used to get the right property 198 | 199 | # Get a list of all properties 200 | $ErrorProperties = Get-ResourceProperty -Object $TestError 201 | 202 | #List of properties that are not relevant 203 | $NotRelevant = @( 204 | '.*PreflightValidationCheck*', 205 | '.*InvalidTemplateDeployment.*' 206 | '.*Preflight validation failed.*' 207 | '.*The template deployment.*' 208 | ) 209 | 210 | foreach ($ErrorProperty in $ErrorProperties.GetEnumerator()) { 211 | if ($ErrorProperty.Key -like "*code*") { 212 | 213 | if ( $ErrorProperty.Value -notmatch ($NotRelevant -join "|") ) { 214 | $ErrorCode = $ErrorProperty.Value 215 | } 216 | } 217 | if ($ErrorProperty.Key -like "*message*") { 218 | if ( $ErrorProperty.Value -notmatch ($NotRelevant -join "|")) { 219 | $ErrorMessage = $ErrorProperty.Value 220 | } 221 | } 222 | } 223 | 224 | if (([string]::IsNullOrEmpty($ErrorCode)) -or ([string]::IsNullOrEmpty($ErrorMessage))) { 225 | Throw "Script could not get the correct error message. Please try again" 226 | } 227 | } 228 | 229 | if (-not [string]::IsNullOrEmpty($Output) ) { 230 | #check if DetailedError has been used. if it is, return the value 231 | if (-not[string]::IsNullOrEmpty($DetailedError)) { 232 | Write-Output "General Error. Find info below:" 233 | Write-Output "ErrorCode: $ErrorCode" 234 | Write-Output "Errormessage: $ErrorMessage" 235 | } 236 | #if not, output the original message 237 | if ([string]::IsNullOrEmpty($DetailedError)) { 238 | Write-Output "Error, Find info below:" 239 | Write-Output $Output.Message 240 | } 241 | #exit code 1 is for Azure DevOps to stop the build in failed state. locally it just stops the script 242 | if ($Pipeline) { 243 | [Environment]::Exit(1) 244 | } 245 | if ($ThrowOnError) { 246 | Throw "Deployment is incorrect" 247 | } 248 | } 249 | else { 250 | Write-Output "deployment is correct" 251 | } 252 | } 253 | 254 | } 255 | -------------------------------------------------------------------------------- /ARMHelper/Public/Test-ARMDeploymentResource.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Gives output that shows all resources that would be deployed by an ARMtemplate 4 | 5 | .DESCRIPTION 6 | When you enter a ARM template and a parameter file, this function will show what would be deployed 7 | To do this, it used the debug output of Test-AzureRmResourceGroupDeployment or Test-AzResourceGroupDeployment. 8 | A list of all the resources is provided with the most important properties. 9 | Some resources have seperated functions to structure the output. 10 | If no function is available, a generic output will be given. 11 | 12 | .PARAMETER ResourceGroup 13 | The resourcegroup where the resources would be deployed to. If it doesn't exist, it will be created 14 | 15 | .PARAMETER TemplateFile 16 | The path to the templatefile 17 | 18 | .PARAMETER TemplateParameterFile 19 | The path to the parameterfile, optional 20 | 21 | .PARAMETER TemplateParameterObject 22 | A Hasbtable with parameters, optional 23 | 24 | .EXAMPLE 25 | Test-ARMDeploymentResource -ResourceGroupName Armtest -TemplateFile .\azuredeploy.json -TemplateParameterFile .\azuredeploy.parameters.json 26 | 27 | -------- 28 | Resource : storageAccounts 29 | Name : armsta12356 30 | Type : Microsoft.Storage/storageAccounts 31 | Location : westeurope 32 | mode : Incremental 33 | ID : /subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/arm/providers/Microsoft.Storage/storageAccounts/armsta12356 34 | 35 | .EXAMPLE 36 | Test-ARMDeploymentResource armtesting .\azuredeploy.json -TemplateParameterObject $parameters | select * 37 | 38 | -------- 39 | Resource : storageAccounts 40 | Name : armsta12356 41 | Type : Microsoft.Storage/storageAccounts 42 | ID : /subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Storage/storageAccounts/armsta12356 43 | Location : westeurope 44 | Tags: ARMcreated : True 45 | accountType : Standard_LRS 46 | apiVersion : 2015-06-15 47 | Tags: displayName : armsta12356 48 | mode : Incremental 49 | 50 | .NOTES 51 | Dynamic Parameters like in the orginal Test-AzResourcegroupDeployment-cmdlet are supported 52 | Script can be used in a CICD pipeline 53 | Author: Barbara Forbes 54 | Module: ARMHelper 55 | https://4bes.nl 56 | @Ba4bes 57 | Source for more output: #Source https://blog.mexia.com.au/testing-arm-templates-with-pester 58 | #> 59 | function Test-ARMDeploymentResource { 60 | [CmdletBinding(DefaultParameterSetName = "__AllParameterSets")] 61 | Param( 62 | [Parameter( 63 | Position = 1, 64 | Mandatory = $true, 65 | ParameterSetName = "__AllParameterSets" 66 | )] 67 | [ValidateNotNullorEmpty()] 68 | [string] $ResourceGroupName, 69 | 70 | [Parameter( 71 | Position = 2, 72 | Mandatory = $true, 73 | ParameterSetName = "__AllParameterSets" 74 | )] 75 | [ValidateNotNullorEmpty()] 76 | [string] $TemplateFile, 77 | 78 | [Parameter( 79 | ParameterSetName = 'TemplateParameterFile', 80 | Mandatory = $true 81 | )] 82 | [string] $TemplateParameterFile, 83 | 84 | [Parameter( 85 | ParameterSetName = 'TemplateParameterObject', 86 | Mandatory = $true 87 | )] 88 | [hashtable] $TemplateParameterObject, 89 | 90 | [parameter ( 91 | ParameterSetName = "__AllParameterSets", 92 | Mandatory = $false 93 | )] 94 | [ValidateSet("Incremental", "Complete")] 95 | [string] $Mode = "Incremental" 96 | ) 97 | DynamicParam { 98 | if ($TemplateFile) { 99 | #create a new ParameterAttribute Object 100 | $OverRideParameter = New-Object System.Management.Automation.ParameterAttribute 101 | $OverRideParameter.Mandatory = $false 102 | #create an attributecollection object for the attribute we just created. 103 | $AttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute] 104 | $AttributeCollection.Add($OverRideParameter) 105 | $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary 106 | $Parameters = (Get-Content $TemplateFile | ConvertFrom-Json).parameters 107 | $ParameterValues = $parameters | Get-Member -MemberType NoteProperty 108 | ForEach ($Param in $ParameterValues) { 109 | $Name = $Param.Name 110 | $type = ($Parameters.$Name).type 111 | #add our paramater specifying the attribute collection 112 | $ExtraParam = New-Object System.Management.Automation.RuntimeDefinedParameter($Param.Name, ($Type -as [type]), $attributeCollection) 113 | 114 | #expose the name of our parameter 115 | 116 | $paramDictionary.Add($Param.Name, $ExtraParam) 117 | } 118 | return $paramDictionary 119 | } 120 | } 121 | process { 122 | 123 | $Parameters = @{ 124 | ResourceGroupName = $ResourceGroupName 125 | TemplateFile = $TemplateFile 126 | Mode = $Mode 127 | } 128 | if (-not[string]::IsNullOrEmpty($TemplateParameterFile) ) { 129 | $Parameters.Add("TemplateParameterFile", $TemplateParameterFile) 130 | } 131 | if (-not[string]::IsNullOrEmpty($TemplateParameterObject) ) { 132 | $Parameters.Add("TemplateParameterObject", $TemplateParameterObject) 133 | } 134 | $CustomParameters = (Get-Content $TemplateFile | ConvertFrom-Json).parameters 135 | $CustomParameterValues = $Customparameters | Get-Member -MemberType NoteProperty 136 | foreach ($param in $CustomParameterValues) { 137 | $paramname = $param.Name 138 | if (-not[string]::IsNullOrEmpty($PSBoundParameters.$paramname)) { 139 | $Key = $paramname 140 | $Value = $PSBoundParameters.$paramname 141 | $Parameters.Add($Key, $Value) 142 | } 143 | } 144 | 145 | $Result = Get-ARMResource @Parameters 146 | if ([string]::IsNullOrEmpty($Result.Mode)) { 147 | Throw "Something is wrong with the output, no resources found. Please check your deployment with Get-ARMdeploymentErrorMessage" 148 | } 149 | 150 | # A list of securestrings is created to mask the output at a later time 151 | $Resultparameters = ($Result.parameters) | get-member -MemberType NoteProperty 152 | $SecureParameters = [System.Collections.Generic.List[string]]::new() 153 | 154 | Foreach ($parameter in $Resultparameters) { 155 | $Type = $result.parameters.$($parameter.Name).Type 156 | If ($Type -eq "SecureString") { 157 | $SecureParameters.Add($Parameter.Name) 158 | } 159 | } 160 | 161 | $ValidatedResources = $Result.ValidatedResources 162 | # Check the module version used, as AZ has limited output 163 | $Module = Test-ARMAzureModule 164 | if ($Module -eq "Az"){ 165 | Write-Warning "The AZ-module is used. This limits the results of this cmdlet. `n 166 | To get full results, consider temporary switching to the AzureRM-module" 167 | } 168 | 169 | #go through each deployed Resource 170 | foreach ($Resource in $ValidatedResources) { 171 | if ([string]::IsNullOrEmpty($Resource.Type) ) { 172 | $Resourceparts = $Resource.Id.Split('/') 173 | $ResourceName = $Resourceparts[-1] 174 | $ResourceType = $Resourceparts[-3] + "/" + $Resourceparts[-2] 175 | $ResourceTypeshort = $Resourceparts[-2] 176 | $ResourceReadable = [PSCustomObject]@{ 177 | Resource = $ResourceTypeShort 178 | Name = $ResourceName 179 | Type = $Resourcetype 180 | ID = $Resource.id 181 | } 182 | } 183 | else { 184 | 185 | $ResourceTypeShort = $($Resource.type.Split("/")[-1]) 186 | 187 | $ResourceReadable = [PSCustomObject] @{ 188 | Resource = $ResourceTypeShort 189 | Name = $Resource.name 190 | Type = $Resource.type 191 | ID = $Resource.id 192 | Location = $Resource.location 193 | } 194 | $PropertiesReadable = Get-ResourceProperty -Object $Resource 195 | 196 | foreach ($Property in $PropertiesReadable.keys) { 197 | $ResourceReadable | Add-Member -MemberType NoteProperty -Name $Property -Value ($PropertiesReadable.$Property) -ErrorAction SilentlyContinue 198 | } 199 | #Add mode when it is not defined 200 | if ([string]::IsNullOrEmpty($ResourceReadable.mode)) { 201 | $ResourceReadable | Add-Member -MemberType NoteProperty -Name "mode" -Value ($Result.mode) -ErrorAction SilentlyContinue 202 | } 203 | $ResourceReadable.PSObject.TypeNames.Insert(0, 'ARMHelper.Default') 204 | } 205 | $ResourceReadable 206 | } 207 | } 208 | } 209 | 210 | 211 | -------------------------------------------------------------------------------- /ARMHelper/Public/Test-ARMexistingResource.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Show if resource that are set to be deployed already exist 4 | 5 | .DESCRIPTION 6 | This function uses Test-AzureRmResourceGroupDeployment or Test-AzResourceGroupDeployment with debug output to find out what resources are deployed. 7 | After that, it checks if those resources exist in Azure. 8 | It will output the results when using complete mode or incremental mode (depending on the ARM template) 9 | 10 | .PARAMETER ResourceGroupName 11 | The resourcegroup where the resources would be deployed to. This resourcegroup needs to exist. 12 | 13 | .PARAMETER TemplateFile 14 | The path to the deploymentfile 15 | 16 | .PARAMETER TemplateParameterFile 17 | The path to the parameterfile 18 | 19 | .PARAMETER Mode 20 | The mode in which the deployment will run. Choose between Incremental or Complete. 21 | Defaults to incremental. 22 | 23 | .PARAMETER ThrowWhenRemoving 24 | This switch makes the function throw when a resources would be overwritten or deleted. This can be useful for use in a pipeline. 25 | 26 | .EXAMPLE 27 | Test-ARMexistingResource -ResourceGroupName ArmTest -TemplateFile .\azuredeploy.json -TemplateParameterFile .\azuredeploy.parameters.json 28 | 29 | -------- 30 | The following resources exist. Mode is set to incremental. New properties might be added: 31 | 32 | type name Current ResourcegroupName 33 | ---- ---- ------------------------- 34 | Microsoft.Storage/storageAccounts armsta armtest 35 | 36 | .NOTES 37 | Dynamic Parameters like in the orginal Test-AzResourcegroupDeployment-cmdlet are supported 38 | Author: Barbara Forbes 39 | Module: ARMHelper 40 | https://4bes.nl 41 | @Ba4bes 42 | #> 43 | Function Test-ARMExistingResource { 44 | [CmdletBinding(DefaultParameterSetName = "__AllParameterSets")] 45 | Param( 46 | [Parameter( 47 | Position = 1, 48 | Mandatory = $true, 49 | ParameterSetName = "__AllParameterSets" 50 | )] 51 | [ValidateNotNullorEmpty()] 52 | [string] $ResourceGroupName, 53 | 54 | [Parameter( 55 | Position = 2, 56 | Mandatory = $true, 57 | ParameterSetName = "__AllParameterSets" 58 | )] 59 | [ValidateNotNullorEmpty()] 60 | [string] $TemplateFile, 61 | 62 | [Parameter( 63 | ParameterSetName = 'TemplateParameterFile', 64 | Mandatory = $true 65 | )] 66 | [string] $TemplateParameterFile, 67 | 68 | [Parameter( 69 | ParameterSetName = 'TemplateParameterObject', 70 | Mandatory = $true 71 | )] 72 | [hashtable] $TemplateParameterObject, 73 | 74 | [parameter ( 75 | ParameterSetName = "__AllParameterSets", 76 | Mandatory = $false 77 | )] 78 | [ValidateSet("Incremental", "Complete")] 79 | [string] $Mode = "Incremental", 80 | 81 | [parameter( 82 | ParameterSetName = "__AllParameterSets" 83 | )] 84 | [switch] $ThrowWhenRemoving 85 | ) 86 | DynamicParam { 87 | if ($TemplateFile) { 88 | #create a new ParameterAttribute Object 89 | $OverRideParameter = New-Object System.Management.Automation.ParameterAttribute 90 | $OverRideParameter.Mandatory = $false 91 | #create an attributecollection object for the attribute we just created. 92 | $AttributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute] 93 | $AttributeCollection.Add($OverRideParameter) 94 | $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary 95 | $Parameters = (Get-Content $TemplateFile | ConvertFrom-Json).parameters 96 | $ParameterValues = $parameters | Get-Member -MemberType NoteProperty 97 | ForEach ($Param in $ParameterValues) { 98 | $Name = $Param.Name 99 | $type = ($Parameters.$Name).type 100 | #add our paramater specifying the attribute collection 101 | $ExtraParam = New-Object System.Management.Automation.RuntimeDefinedParameter($Param.Name, ($Type -as [type]), $attributeCollection) 102 | 103 | #expose the name of our parameter 104 | 105 | $paramDictionary.Add($Param.Name, $ExtraParam) 106 | } 107 | return $paramDictionary 108 | } 109 | } 110 | process { 111 | 112 | #set variables 113 | $Parameters = @{ 114 | ResourceGroupName = $ResourceGroupName 115 | TemplateFile = $TemplateFile 116 | Mode = $Mode 117 | } 118 | 119 | if (-not[string]::IsNullOrEmpty($TemplateParameterFile) ) { 120 | $Parameters.Add("TemplateParameterFile", $TemplateParameterFile) 121 | } 122 | if (-not[string]::IsNullOrEmpty($TemplateParameterObject) ) { 123 | $Parameters.Add("TemplateParameterObject", $TemplateParameterObject) 124 | } 125 | $CustomParameters = (Get-Content $TemplateFile | ConvertFrom-Json).parameters 126 | $CustomParameterValues = $Customparameters | Get-Member -MemberType NoteProperty 127 | foreach ($param in $CustomParameterValues) { 128 | $paramname = $param.Name 129 | if (-not[string]::IsNullOrEmpty($PSBoundParameters.$paramname)) { 130 | $Key = $paramname 131 | $Value = $PSBoundParameters.$paramname 132 | $Parameters.Add($Key, $Value) 133 | } 134 | } 135 | 136 | #Get the AzureModule that's being used 137 | $Module = Test-ARMAzureModule 138 | 139 | $Result = Get-ARMResource @Parameters 140 | 141 | if ([string]::IsNullOrEmpty($Result.Mode)) { 142 | Throw "Something is wrong with the output, no resources found. Please check your deployment with Get-ARMdeploymentErrorMessage" 143 | } 144 | #tell the user if de mode is complete or incremental 145 | Write-Output "Mode for deployment is $($Result.Mode) `n" 146 | 147 | $ValidatedResources = $Result.ValidatedResources 148 | $NewResources = [System.Collections.ArrayList]@() 149 | $ExistingResources = [System.Collections.ArrayList]@() 150 | $DeletedResources = [System.Collections.ArrayList]@() 151 | $OverwrittenResources = [System.Collections.ArrayList]@() 152 | $DifferentResourcegroup = [System.Collections.ArrayList]@() 153 | if ($Module -eq "Az") { 154 | $CheckRGResources = Get-AzResource -ResourceGroupName $ResourceGroupName 155 | } 156 | elseif ($Module -eq "AzureRM") { 157 | $CheckRGResources = Get-AzureRmResource -ResourceGroupName $ResourceGroupName 158 | } 159 | else { 160 | Throw "Something went wrong, No AzureRM of AZ module found" 161 | } 162 | foreach ($CheckRGResource in $CheckRGResources) { 163 | if ($ValidatedResources.Id -notcontains $CheckRGResource.ResourceId -and $Mode -eq "Complete") { 164 | Write-Verbose "Resource $($Resource.name) exists in the resourcegroup and mode is set to Complete" 165 | Write-Verbose "RESOURCE WILL BE DELETED!" 166 | $CheckRGResource.PSObject.TypeNames.Insert(0, 'ArmHelper.ExistingResource') 167 | $null = $DeletedResources.Add($CheckRGResource) 168 | } 169 | } 170 | 171 | foreach ($Resource in $ValidatedResources) { 172 | $Resourceparts = $Resource.Id.Split('/') 173 | if ([string]::IsNullOrEmpty($resource.type)){ 174 | $Resource | add-member -MemberType NoteProperty -Name "Name" -Value $Resourceparts[-1] 175 | $resource | Add-Member -MemberType NoteProperty -Name "Type" -Value ($Resourceparts[-3] + "/" + $Resourceparts[-2]) 176 | } 177 | #Give a warning that Deployments can give unexpected results 178 | If ($Resource.Type -eq "Microsoft.Resources/deployments") { 179 | Write-Warning "This command does not work for the resourcetype Microsoft.Resources/deployments. Please check $($Resource.Name) manually." 180 | Continue 181 | } 182 | if ($Module -eq "Az") { 183 | $Check = Get-AzResource -Name $Resource.name -ResourceType $Resource.type 184 | } 185 | elseif ($Module -eq "AzureRM") { 186 | $Check = Get-AzureRmResource -Name $Resource.name -ResourceType $Resource.type 187 | } 188 | else { 189 | Throw "Something went wrong, No AzureRM of AZ module found" 190 | } 191 | if ([string]::IsNullOrEmpty($Check)) { 192 | Write-Verbose "Resource $($Resource.name) does not exist, it will be created" 193 | $Resource.PSObject.TypeNames.Insert(0, 'ArmHelper.ExistingResource') 194 | $Resource | Add-Member -MemberType NoteProperty -Name "ResourceGroupName" -Value ($ResourceGroupName) -ErrorAction SilentlyContinue 195 | $null = $NewResources.Add($Resource) 196 | } 197 | else { 198 | if ($Check.ResourceGroupName -eq $ResourceGroupName ) { 199 | if ($Result.Mode -eq "Complete") { 200 | Write-Verbose "Resource $($Resource.name) already exists and mode is set to Complete" 201 | Write-Verbose "RESOURCE WILL BE OVERWRITTEN!" 202 | $Resource.PSObject.TypeNames.Insert(0, 'ArmHelper.ExistingResource') 203 | $Resource | Add-Member -MemberType NoteProperty -Name "ResourceGroupName" -Value ($ResourceGroupName) -ErrorAction SilentlyContinue 204 | $null = $OverwrittenResources.Add($Resource) 205 | } 206 | elseif ($Result.Mode -eq "Incremental") { 207 | Write-Verbose "Resource $($Resource.name) already exists, mode is set to incremental" 208 | Write-Verbose "New properties might be added" 209 | $Resource.PSObject.TypeNames.Insert(0, 'ArmHelper.ExistingResource') 210 | $Resource | Add-Member -MemberType NoteProperty -Name "ResourceGroupName" -Value ($ResourceGroupName) -ErrorAction SilentlyContinue 211 | $null = $ExistingResources.Add($Resource) 212 | } 213 | else { 214 | Write-Error "Resource mode for $($Resource.name) is not clear, please check manually" 215 | } 216 | } 217 | else { 218 | Write-Verbose "$($Resource.name) exists, but in another ResourceGroup. Deployment might fail." 219 | $Resource.PSObject.TypeNames.Insert(0, 'ArmHelper.ExistingResource') 220 | $Resource | Add-Member -MemberType NoteProperty -Name "ResourceGroupName" -Value ($Check.ResourceGroupName) -ErrorAction SilentlyContinue 221 | $null = $DifferentResourcegroup.Add($Resource) 222 | } 223 | } 224 | } 225 | if ($NewResources.count -ne 0) { 226 | Write-Output "The following resources do not exist and will be created:" 227 | $NewResources 228 | Write-Output "" 229 | } 230 | 231 | if ($ExistingResources.count -ne 0) { 232 | Write-Output "The following resources exist. Mode is set to incremental. New properties might be added:" 233 | $ExistingResources 234 | Write-Output "" 235 | } 236 | 237 | if ($OverwrittenResources.Count -ne 0) { 238 | Write-Output "THE FOLLOWING RESOURCES WILL BE OVERWRITTEN! `n Resources exist and mode is complete:" 239 | $OverwrittenResources 240 | Write-Output "" 241 | if ($ThrowWhenRemoving) { 242 | Throw "Resources will be deleted or overwritten." 243 | } 244 | } 245 | 246 | if ($DeletedResources.Count -ne 0) { 247 | Write-Output "THE FOLLOWING RESOURCES WILL BE DELETED! `n Resources exist in the resourcegroup but not in the template, mode is complete:" 248 | $DeletedResources 249 | Write-Output "" 250 | if ($ThrowWhenRemoving) { 251 | Throw "Resources will be deleted or overwritten." 252 | } 253 | } 254 | if ($DifferentResourcegroup.Count -ne 0) { 255 | Write-Output "A resource of the same type and same name exists in other resourcegroup(s). This deployment might fail.`n" 256 | $DifferentResourcegroup 257 | Write-Output "" 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ##[0.6.3] - 2019-10-12 9 | 10 | ### Fixed 11 | 12 | - A change in the Az Module broke most module output. Workarounds have been implemented. 13 | - 14 | ##[0.6.2] 15 | 16 | ### Added 17 | 18 | - Support for overriding parameters #26 19 | 20 | ### Fixed 21 | 22 | - A warning is added to Test-ARMExistingResource as this module can't support Microsoft.Resources/deployments at this time (30) 23 | - Get-ARMDeploymentErrorMessage had issues lately because the output from the Azure Log is not always consistent. It now searches for the right properties in a different way (#32) 24 | 25 | ## [0.5.7] - 2019-08-15 26 | 27 | ### Fixed 28 | 29 | - The check for AzureRm/Az broke for the Azure DevOps pipeline. This is fixed 30 | This also fixes #23 as Az is now the preferred module 31 | 32 | ## [0.5.6] - 2019-07-20 33 | 34 | ### Added 35 | 36 | - Pester testing has been implemented and added to the pipeline #8 37 | - Support for TemplateParameterObject and no parameters at all #5 38 | 39 | ### Fixed 40 | 41 | - The pipeline was broken. It was fixed and improved #18 42 | - SecureString was handled as plain text. The output now shows securestring #19 43 | - minor fixes, like the commenthelp for Test-ARMExistingResource being wrong 44 | 45 | ## [0.3.4] - 2019-06-11 46 | 47 | ### Added 48 | 49 | - PesterTest for Get-ArmdeploymentErrorMessage #8 50 | 51 | ### Fixed 52 | 53 | - Bugfix for Get-ARMDeploymentErrorMessage ending in a timeout #14 54 | 55 | ## [0.3.3] - 2019-05-08 56 | 57 | ### added 58 | 59 | - Support for the AZ module as well as the AzureRM module. #4 60 | 61 | ### Fixed 62 | 63 | - Bugfix to get rid of empty values in Test-ArmDeploymentResource 64 | - Bugfix for empty errormessages in Get-ARMDeploymentResource 65 | - Bugfix Test-ARMDeploymentResource: Network security group output is wrong #3 66 | 67 | ## [0.2.1] - 2019-04-18 68 | 69 | ### added 70 | 71 | - Pipelinesupport for Test-ARMExistingResource 72 | - Changed Get-ArmdeploymentErrormessage switch for pipeline to general throw 73 | 74 | ### Fixed 75 | 76 | - Test-ARMExistingResource showed overwrite-output when mode was nog complete 77 | 78 | ## [0.1.1] - 2019-04-11 79 | 80 | ### Fixed 81 | 82 | - #9 Test-ARMExistingResource should output all resource group changes with complete switch 83 | 84 | ## [0.1.0] - 2019-04-11 85 | 86 | ### Added 87 | 88 | - Test-ARMDEploymentResource now shows "tags" in front of a tag for readability 89 | 90 | ## [0.0.1] - 2019-03-30 91 | 92 | ### Added 93 | 94 | - initial commit 95 | 96 | --- 97 | 98 | 99 | Cheatcheet: 100 | 101 | **Added** for new features. 102 | **Changed** for changes in existing functionality. 103 | **Deprecated** for soon-to-be removed features. 104 | **Removed** for now removed features. 105 | **Fixed** for any bug fixes. 106 | **Security** in case of vulnerabilities. 107 | -------------------------------------------------------------------------------- /Devazure-pipeline.yml: -------------------------------------------------------------------------------- 1 | name: Development 2 | 3 | trigger: 4 | branches: 5 | include: 6 | - '*' # must quote since "*" is a YAML reserved character; we want a string 7 | 8 | stages: 9 | 10 | - stage: Build 11 | jobs: 12 | - job: BuildWindows 13 | pool: 14 | vmImage: windows-2019 15 | steps: 16 | - template: azure-pipelinestemplate.yml 17 | parameters: 18 | platform: 'Windows' 19 | - template: azure-pipelinesazuretemplate.yml 20 | parameters: 21 | platform: 'Windows' 22 | - job: BuildMacOS 23 | pool: 24 | vmImage: macOS-10.14 25 | steps: 26 | - template: azure-pipelinestemplate.yml 27 | parameters: 28 | platform: 'MacOS' 29 | - job: BuildLinux 30 | pool: 31 | vmImage: ubuntu-16.04 32 | steps: 33 | - template: azure-pipelinestemplate.yml 34 | parameters: 35 | platform: 'Linux' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Barbara 4bes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ARMHelper 2 | 3 | ### Development Status 4 | 5 | [![Build Status](https://dev.azure.com/Ba4bes/ARMHelper/_apis/build/status/Development?branchName=Development)](https://dev.azure.com/Ba4bes/ARMHelper/_build/latest?definitionId=17&branchName=Development) 6 | ![Azure DevOps tests (branch)](https://img.shields.io/azure-devops/tests/ba4bes/armhelper/17/Development.svg) 7 | 8 | ### Production status 9 | 10 | [![Build Status](https://dev.azure.com/Ba4bes/ARMHelper/_apis/build/status/Master?branchName=master)](https://dev.azure.com/Ba4bes/ARMHelper/_build/latest?definitionId=8&branchName=master) ![GitHub issues](https://img.shields.io/github/issues-raw/ba4bes/ARMHelper.svg) 11 | 12 | [![Gallery version](https://img.shields.io/powershellgallery/v/ARMHelper.svg)](https://img.shields.io/powershellgallery/v/ARMHelper.svg) 13 | [![Download Status](https://img.shields.io/powershellgallery/dt/ARMHelper.svg)](https://img.shields.io/powershellgallery/dt/ARMHelper.svg) 14 | 15 | ## Introduction 16 | 17 | This module is created to make it easier to work with ARM deployments. 18 | 19 | You are at this point able to: 20 | 21 | - Get detailed errormessages when Azure gives general errors 22 | - Get an overview of all the resources that would get deployed 23 | - Check if resources that you want to deploy already exist and whether they would be overwritten. 24 | 25 | For an introduction, please view 26 | 27 | 28 | For usage in an Azure DevOps pipeline: 29 | 30 | ## Common setup 31 | 32 | ### prerequisites 33 | 34 | - Access to an Azure subscription 35 | - Have the AzureRM or AZ module installed and a connection ready 36 | - Windows PowerShell or PowerShell Core. MacOS and Linux are tested in the pipeline, but could have issues. Please let me know if you run in to one 37 | 38 | ### Installation 39 | 40 | Install ARMHelper from the [PowerShell Gallery](https://powershellgallery.com): 41 | 42 | ```powershell 43 | Install-Module -Name ARMHelper -Scope CurrentUser 44 | Import-Module ARMHelper 45 | ``` 46 | 47 | ## Examples 48 | 49 | ### Get a detailed output for a general error 50 | 51 | ```cmd 52 | C:\> Get-ARMDeploymentErrorMessage -ResourceGroupName ARMTest -TemplateFile .\azuredeploy.json -TemplateParameterFile .\azuredeploy.parameters.json 53 | 54 | the output is a generic error message. The log is searched for a more clear errormessage 55 | General Error. Find info below: 56 | ErrorCode: AccountNameInvalid 57 | Errormessage: arm-aqkc32cvb2qmmw is not a valid storage account name. Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only. 58 | ``` 59 | 60 | ### Get an overview of the new resources that would be deployed 61 | 62 | ```cmd 63 | C:\> Test-ARMDeploymentResource -ResourceGroupName ARMTest -TemplateFile .\azuredeploy.json -TemplateParameterFile .\azuredeploy.parameters.json 64 | 65 | Mode for deployment is Incremental 66 | The following Resources will be deployed: 67 | 68 | 69 | Resource: storageAccounts 70 | 71 | Name : armaqkc32cvb2qmmw 72 | Type : Microsoft.Storage/storageAccounts 73 | ID : /subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Storage/storageAccounts/armaqkc32cvb2qmmw 74 | Location : westeurope 75 | accountType : Standard_LRS 76 | apiVersion : 2015-06-15 77 | ARMcreated : True 78 | displayName : armaqkc32cvb2qmmw 79 | ``` 80 | 81 | ### Test if a Resource exists already 82 | 83 | ```cmd 84 | C:\> Test-ARMExistingResource -ResourceGroupName armtesting -TemplateFile .\azuredeploy.json -TemplateParameterFile .\azuredeploy.parameters.json 85 | 86 | Mode for deployment is Incremental 87 | The following resources do not exist and will be created 88 | armaqkc32cvb2qmmw 89 | 90 | C:\> Test-ARMExistingResource -ResourceGroupName armtesting -TemplateFile .\azuredeploy.json -TemplateParameterFile .\azuredeploy.parameters.json 91 | 92 | Mode for deployment is Incremental 93 | The following resources exist. Mode is set to incremental. New properties might be added 94 | armaqkc32cvb2qmmw 95 | 96 | ``` 97 | 98 | See Docs-folder for more documentation. (Generated with PlatyPS ) 99 | 100 | ## Change Log 101 | 102 | View Change log [here](CHANGELOG.md) 103 | 104 | ## To Contribute 105 | 106 | Any ideas or contributions are welcome! 107 | Please add an issue with your suggestions. 108 | 109 | ## Known Issues 110 | 111 | View known issues [here](https://github.com/Ba4bes/ARMHelper/issues) 112 | -------------------------------------------------------------------------------- /Tests/AzureTesting/Get-ARMDEploymentErrorMessage.tests.ps1: -------------------------------------------------------------------------------- 1 | $projectRoot = Resolve-Path "$PSScriptRoot\..\.." 2 | $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") 3 | $moduleName = Split-Path $moduleRoot -Leaf 4 | if (Get-Module ARMHelper) { 5 | Remove-Module -Name ArmHelper -Force 6 | } 7 | 8 | 9 | Import-Module (Join-Path $moduleRoot "$moduleName.psd1") -Force 10 | 11 | Describe 'Check Get-ARMDEploymentErrorMessage with Azure' -Tag @("Az") { 12 | InModuleScope ARMHelper { 13 | Context 'Basic functionality' { 14 | It "When a deployment is correct, output is deployment is correct" { 15 | $Parameters = @{ 16 | resourcegroupname = "ArmHelper" 17 | templatefile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.json" 18 | templateparameterfile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.parameters.json" 19 | } 20 | $Result = Get-ARMDeploymentErrorMessage @Parameters 21 | $Result | Should -Be "deployment is correct" 22 | } 23 | 24 | It "Works with a parameterFile" { 25 | $Parameters = @{ 26 | resourcegroupname = "ArmHelper" 27 | templatefile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.json" 28 | templateparameterfile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.parameters.json" 29 | } 30 | $Result = Get-ARMDeploymentErrorMessage @Parameters 31 | $Result | Should -Be "deployment is correct" 32 | } 33 | It "works with a parameter object" { 34 | $Parameterobject = @{ 35 | storageAccountPrefix = "armsta" 36 | storageAccountType = "Standard_LRS" 37 | } 38 | $Parameters = @{ 39 | resourcegroupname = "ArmHelper" 40 | templatefile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.json" 41 | templateparameterobject = $Parameterobject 42 | } 43 | $Result = Get-ARMDeploymentErrorMessage @Parameters 44 | $Result | Should -Be "deployment is correct" 45 | } 46 | It "works with added Parameters" { 47 | $Parameters = @{ 48 | resourcegroupname = "ArmHelper" 49 | templatefile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.json" 50 | storageAccountPrefix = "armsta" 51 | storageAccountType = "Standard_LRS" 52 | } 53 | $Result = Get-ARMDeploymentErrorMessage @Parameters 54 | $Result | Should -Be "deployment is correct" 55 | } 56 | 57 | It "When deployment has a regular error, it is given" { 58 | $Parameters = @{ 59 | resourcegroupname = "ArmHelper" 60 | templatefile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.json" 61 | templateparameterfile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.parameters.json" 62 | } 63 | $Test = Get-ARMDeploymentErrorMessage @Parameters 64 | $Test[0] | Should -Be "Error, Find info below:" 65 | $Test[1] | Should -Be "Deployment template validation failed: 'The template resource '[variables('storageAccountName')]' at line '32' and column '9' is not valid. The type property is invalid. Please see https://aka.ms/arm-template/#resources for usage details.'." 66 | } 67 | 68 | It "When deployment has a general error, the right results are given" { 69 | $Parameters = @{ 70 | resourcegroupname = "ArmHelper" 71 | templatefile = "$PSScriptRoot\StorageAccountGE\azuredeploy.json" 72 | templateparameterfile = "$PSScriptRoot\StorageAccountGE\azuredeploy.parameters.json" 73 | } 74 | $Result = Get-ARMDeploymentErrorMessage @Parameters 75 | $Result[0] | Should -Be "the output is a generic error message. The log is searched for a more clear errormessage" 76 | $Result[-3] | Should -Be "General Error. Find info below:" 77 | $Result[-2] | Should -Be "ErrorCode: AccountNameInvalid" 78 | $Result[-1] | Should -BeLike "* is not a valid storage account name. Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only." 79 | } 80 | 81 | Start-Sleep 5 82 | It "Throws when TrowonError is used" { 83 | $Parameters = @{ 84 | resourcegroupname = "ArmHelper" 85 | templatefile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.json" 86 | templateparameterfile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.parameters.json" 87 | } 88 | { Get-ARMDeploymentErrorMessage @Parameters -ThrowOnError } | Should -Throw "Deployment is incorrect" 89 | } 90 | It "When no errormessage is found in azurelog, script throws" { 91 | Mock Start-Sleep { $null } 92 | function Get-AzureRMLog([String]$Name, [Object]$Value, [Switch]$Clobber) { } 93 | function Get-AzLog([String]$Name, [Object]$Value, [Switch]$Clobber) { } 94 | Mock Get-AzureRMLog { $null } 95 | Mock Get-AzLog { $null } 96 | $Parameters = @{ 97 | resourcegroupname = "ArmHelper" 98 | templatefile = "$PSScriptRoot\StorageAccountGE\azuredeploy.json" 99 | templateparameterfile = "$PSScriptRoot\StorageAccountGE\azuredeploy.parameters.json" 100 | } 101 | { Get-ARMDeploymentErrorMessage @Parameters } | Should -Throw "Can't get Azure Log Entry. Please check the log manually in the portal." 102 | } 103 | 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Tests/AzureTesting/StorageAccountBroken/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "storageAccountPrefix": { 6 | "type": "string", 7 | "maxLength": 11, 8 | "defaultValue": "armsta", 9 | "metadata": { 10 | "description": "Prefix the storageaccount" 11 | } 12 | }, 13 | "storageAccountType": { 14 | "type": "string", 15 | "defaultValue": "Standard_LRS", 16 | "allowedValues": [ 17 | "Standard_LRS", 18 | "Premium_LRS", 19 | "Standard_RAGRS" 20 | ], 21 | "metadata": { 22 | "description": "The type for the storage account" 23 | } 24 | } 25 | }, 26 | "variables": { 27 | "storageAccountName": "[toLower(concat(parameters('storageAccountPrefix'), uniqueString(resourceGroup().id)))]" 28 | }, 29 | "resources": [ 30 | { 31 | "type": "Microsoft.", 32 | "apiVersion": "2015-06-15", 33 | "name": "[variables('storageAccountName')]", 34 | "location": "[resourceGroup().location]", 35 | "tags": { 36 | "displayName": "[variables('storageAccountName')]", 37 | "ARMcreated": "True" 38 | }, 39 | "properties": { 40 | "accountType": "[parameters('storageAccountType')]" 41 | } 42 | } 43 | ], 44 | "outputs": { 45 | "storageAccount": { 46 | "type": "string", 47 | "value": "[variables('storageAccountName')]" 48 | } 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /Tests/AzureTesting/StorageAccountBroken/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "storageAccountPrefix": { 6 | "value": "armsta" 7 | }, 8 | "storageAccountType": { 9 | "value": "Standard_LRS" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Tests/AzureTesting/StorageAccountFixed/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "storageAccountPrefix": { 6 | "type": "string", 7 | "maxLength": 11, 8 | "defaultValue": "armsta", 9 | "metadata": { 10 | "description": "Prefix the storageaccount" 11 | } 12 | }, 13 | "storageAccountType": { 14 | "type": "string", 15 | "defaultValue": "Standard_LRS", 16 | "allowedValues": [ 17 | "Standard_LRS", 18 | "Premium_LRS", 19 | "Standard_RAGRS" 20 | ], 21 | "metadata": { 22 | "description": "The type for the storage account" 23 | } 24 | } 25 | }, 26 | "variables": { 27 | "storageAccountName": "[toLower(concat(parameters('storageAccountPrefix'), uniqueString(resourceGroup().id)))]" 28 | }, 29 | "resources": [ 30 | { 31 | "type": "Microsoft.Storage/storageAccounts", 32 | "apiVersion": "2015-06-15", 33 | "name": "[variables('storageAccountName')]", 34 | "location": "[resourceGroup().location]", 35 | "tags": { 36 | "displayName": "[variables('storageAccountName')]", 37 | "ARMcreated": "True" 38 | }, 39 | "properties": { 40 | "accountType": "[parameters('storageAccountType')]" 41 | } 42 | } 43 | ], 44 | "outputs": { 45 | "storageAccount": { 46 | "type": "string", 47 | "value": "[variables('storageAccountName')]" 48 | } 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /Tests/AzureTesting/StorageAccountFixed/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "storageAccountPrefix": { 6 | "value": "armsta" 7 | }, 8 | "storageAccountType": { 9 | "value": "Standard_LRS" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Tests/AzureTesting/StorageAccountGE/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "storageAccountPrefix": { 6 | "type": "string", 7 | "maxLength": 11, 8 | "defaultValue": "armsta", 9 | "metadata": { 10 | "description": "Prefix the storageaccount" 11 | } 12 | }, 13 | "storageAccountType": { 14 | "type": "string", 15 | "defaultValue": "Standard_LRS", 16 | "allowedValues": [ 17 | "Standard_LRS", 18 | "Premium_LRS", 19 | "Standard_RAGRS" 20 | ], 21 | "metadata": { 22 | "description": "The type for the storage account" 23 | } 24 | } 25 | }, 26 | "variables": { 27 | "storageAccountName": "[toLower(concat(parameters('storageAccountPrefix'), uniqueString(resourceGroup().id)))]" 28 | }, 29 | "resources": [ 30 | { 31 | "type": "Microsoft.Storage/storageAccounts", 32 | "apiVersion": "2015-06-15", 33 | "name": "[variables('storageAccountName')]", 34 | "location": "[resourceGroup().location]", 35 | "tags": { 36 | "displayName": "[variables('storageAccountName')]", 37 | "ARMcreated": "True" 38 | }, 39 | "properties": { 40 | "accountType": "[parameters('storageAccountType')]" 41 | } 42 | } 43 | ], 44 | "outputs": { 45 | "storageAccount": { 46 | "type": "string", 47 | "value": "[variables('storageAccountName')]" 48 | } 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /Tests/AzureTesting/StorageAccountGE/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "storageAccountPrefix": { 6 | "value": "sa!" 7 | }, 8 | "storageAccountType": { 9 | "value": "Standard_LRS" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Tests/AzureTesting/Test-ARMDeploymentResource.tests.ps1: -------------------------------------------------------------------------------- 1 | $projectRoot = Resolve-Path "$PSScriptRoot\..\.." 2 | $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") 3 | $moduleName = Split-Path $moduleRoot -Leaf 4 | if (Get-Module ARMHelper) { 5 | Remove-Module -Name ARMHelper -Force 6 | } 7 | Import-Module (Join-Path $moduleRoot "$moduleName.psd1") -Force 8 | 9 | 10 | Describe 'Check Test-ARMDeploymentResource with Azure' -Tag @("Az") { 11 | InModuleScope ARMHelper { 12 | $Parameters = @{ 13 | resourcegroupname = "ArmHelper" 14 | templatefile = "$PSScriptRoot\VirtualMachine\azuredeploy.json" 15 | templateparameterfile = "$PSScriptRoot\VirtualMachine\azuredeploy.parameters.json" 16 | } 17 | Context 'Basic functionallity' { 18 | 19 | it "Gives a warning for using Az-module"{ 20 | $Result = (Test-ARMDeploymentResource @Parameters 3>&1) 21 | if ($Module -eq "Az"){ 22 | $Result.Message[0] | Should -BeLike "The AZ-module is used. This limits the results of this cmdlet*" 23 | } 24 | if ($Module -eq "AzureRM"){ 25 | $Result.Message[0] | Should -Not -BeLike "The AZ-module is used. This limits the results of this cmdlet*" 26 | } 27 | } 28 | It "Works with a parameterFile" { 29 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 30 | } 31 | It "works with a parameter object" { 32 | $Parameterobject = @{ 33 | adminUsername = "Test" 34 | adminPassword = "Welkom123" 35 | dnsLabelPrefix = "eandomrlkajelnadada" 36 | } 37 | $Parameters = @{ 38 | resourcegroupname = "ArmHelper" 39 | templatefile = "$PSScriptRoot\VirtualMachine\azuredeploy.json" 40 | templateparameterobject = $Parameterobject 41 | } 42 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 43 | 44 | } 45 | It "works with added Parameters" { 46 | $Parameters = @{ 47 | resourcegroupname = "ArmHelper" 48 | templatefile = "$PSScriptRoot\VirtualMachine\azuredeploy.json" 49 | adminPassword = ( "VeryNicePassword" | ConvertTo-SecureString -AsPlainText -force ) 50 | adminUsername = "Test" 51 | dnsLabelPrefix = "eandomrlkajelnadada" 52 | } 53 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 54 | } 55 | It "When a deployment is correct, script doesn't throw" { 56 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 57 | } 58 | It "Shows standard properties for resources that would be in the deployment" { 59 | $Result = Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue 60 | if ($Module -eq "AzureRM"){ 61 | $Result[0].Resource | Should -be "StorageAccounts" 62 | $Result[0].Name | Should -be "mstaqkc32c2qmmw" 63 | $Result[0].Type | Should -be "Microsoft.Storage/storageAccounts" 64 | $Result[0].Location | Should -be "westeurope" 65 | $Result[0].mode | Should -be "Incremental" 66 | $Result[0].ID | Should -BeLike "*/resourceGroups/ArmHelper/providers/Microsoft.Storage/storageAccounts/mstaqkc32c2qmmw" 67 | $Result[0].kind | Should -be "Storage" 68 | $Result[0].sku.name | Should -BeNullOrEmpty 69 | } 70 | if ($Module -eq "Az"){ 71 | $Result[0].Resource | Should -be "StorageAccounts" 72 | $Result[0].Name | Should -be "mstaqkc32c2qmmw" 73 | $Result[0].Type | Should -be "Microsoft.Storage/storageAccounts" 74 | $Result[0].ID | Should -BeLike "*/resourceGroups/ArmHelper/providers/Microsoft.Storage/storageAccounts/mstaqkc32c2qmmw" 75 | 76 | } 77 | } 78 | if ($Module -eq "AzureRM"){ 79 | It "Shows all properties when using Select-Object *" { 80 | if ($Module -eq "AzureRM"){ 81 | $Result = Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue | Select-Object * 82 | $result[0].'.sku.name' | Should -be "Standard_LRS" 83 | $Result[0].apiVersion | Should -be "2018-11-01" 84 | 85 | } 86 | it "When a parameter is a securestring, it is shown as a securestring in the output" { 87 | 88 | $Result = Test-ARMDeploymentResource -WarningAction SilentlyContinue @Parameters 89 | $Result[-1].adminPassword | Should -Be "System.Security.SecureString" 90 | } 91 | } 92 | } 93 | it "Should throw when no result is found" { 94 | $Parameters = @{ 95 | resourcegroupname = "ArmHelper" 96 | templatefile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.json" 97 | templateparameterfile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.parameters.json" 98 | } 99 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should throw "Something is wrong with the output, no resources found. Please check your deployment with Get-ARMdeploymentErrorMessage" 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Tests/AzureTesting/VirtualMachine/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "adminUsername": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "Username for the Virtual Machine." 9 | } 10 | }, 11 | "adminPassword": { 12 | "type": "securestring", 13 | "metadata": { 14 | "description": "Password for the Virtual Machine." 15 | } 16 | }, 17 | "dnsLabelPrefix": { 18 | "type": "string", 19 | "metadata": { 20 | "description": "Unique DNS Name for the Public IP used to access the Virtual Machine." 21 | } 22 | }, 23 | "windowsOSVersion": { 24 | "type": "string", 25 | "defaultValue": "2016-Datacenter", 26 | "allowedValues": [ 27 | "2008-R2-SP1", 28 | "2012-Datacenter", 29 | "2012-R2-Datacenter", 30 | "2016-Nano-Server", 31 | "2016-Datacenter-with-Containers", 32 | "2016-Datacenter", 33 | "2019-Datacenter" 34 | ], 35 | "metadata": { 36 | "description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version." 37 | } 38 | }, 39 | "location": { 40 | "type": "string", 41 | "defaultValue": "[resourceGroup().location]", 42 | "metadata": { 43 | "description": "Location for all resources." 44 | } 45 | } 46 | }, 47 | "variables": { 48 | "storageAccountName": "mstaqkc32c2qmmw", 49 | "nicName": "myVMNic", 50 | "addressPrefix": "10.0.0.0/16", 51 | "subnetName": "Subnet", 52 | "subnetPrefix": "10.0.0.0/24", 53 | "publicIPAddressName": "myPublicIP", 54 | "vmName": "SimpleWinVM", 55 | "virtualNetworkName": "MyVNET", 56 | "subnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('virtualNetworkName'), variables('subnetName'))]" 57 | }, 58 | "resources": [ 59 | { 60 | "type": "Microsoft.Storage/storageAccounts", 61 | "apiVersion": "2018-11-01", 62 | "name": "[variables('storageAccountName')]", 63 | "location": "[parameters('location')]", 64 | "sku": { 65 | "name": "Standard_LRS" 66 | }, 67 | "kind": "Storage", 68 | "properties": {} 69 | }, 70 | { 71 | "type": "Microsoft.Network/publicIPAddresses", 72 | "apiVersion": "2018-11-01", 73 | "name": "[variables('publicIPAddressName')]", 74 | "location": "[parameters('location')]", 75 | "properties": { 76 | "publicIPAllocationMethod": "Dynamic", 77 | "dnsSettings": { 78 | "domainNameLabel": "[parameters('dnsLabelPrefix')]" 79 | } 80 | } 81 | }, 82 | { 83 | "type": "Microsoft.Network/virtualNetworks", 84 | "apiVersion": "2018-11-01", 85 | "name": "[variables('virtualNetworkName')]", 86 | "location": "[parameters('location')]", 87 | "properties": { 88 | "addressSpace": { 89 | "addressPrefixes": [ 90 | "[variables('addressPrefix')]" 91 | ] 92 | }, 93 | "subnets": [ 94 | { 95 | "name": "[variables('subnetName')]", 96 | "properties": { 97 | "addressPrefix": "[variables('subnetPrefix')]" 98 | } 99 | } 100 | ] 101 | } 102 | }, 103 | { 104 | "type": "Microsoft.Network/networkInterfaces", 105 | "apiVersion": "2018-11-01", 106 | "name": "[variables('nicName')]", 107 | "location": "[parameters('location')]", 108 | "dependsOn": [ 109 | "[resourceId('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", 110 | "[resourceId('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" 111 | ], 112 | "properties": { 113 | "ipConfigurations": [ 114 | { 115 | "name": "ipconfig1", 116 | "properties": { 117 | "privateIPAllocationMethod": "Dynamic", 118 | "publicIPAddress": { 119 | "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" 120 | }, 121 | "subnet": { 122 | "id": "[variables('subnetRef')]" 123 | } 124 | } 125 | } 126 | ] 127 | } 128 | }, 129 | { 130 | "type": "Microsoft.Compute/virtualMachines", 131 | "apiVersion": "2018-10-01", 132 | "name": "[variables('vmName')]", 133 | "location": "[parameters('location')]", 134 | "dependsOn": [ 135 | "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", 136 | "[resourceId('Microsoft.Network/networkInterfaces/', variables('nicName'))]" 137 | ], 138 | "properties": { 139 | "hardwareProfile": { 140 | "vmSize": "Standard_A2" 141 | }, 142 | "osProfile": { 143 | "computerName": "[variables('vmName')]", 144 | "adminUsername": "[parameters('adminUsername')]", 145 | "adminPassword": "[parameters('adminPassword')]" 146 | }, 147 | "storageProfile": { 148 | "imageReference": { 149 | "publisher": "MicrosoftWindowsServer", 150 | "offer": "WindowsServer", 151 | "sku": "[parameters('windowsOSVersion')]", 152 | "version": "latest" 153 | }, 154 | "osDisk": { 155 | "createOption": "FromImage" 156 | }, 157 | "dataDisks": [ 158 | { 159 | "diskSizeGB": 1023, 160 | "lun": 0, 161 | "createOption": "Empty" 162 | } 163 | ] 164 | }, 165 | "networkProfile": { 166 | "networkInterfaces": [ 167 | { 168 | "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" 169 | } 170 | ] 171 | }, 172 | "diagnosticsProfile": { 173 | "bootDiagnostics": { 174 | "enabled": true, 175 | "storageUri": "[reference(resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))).primaryEndpoints.blob]" 176 | } 177 | } 178 | } 179 | } 180 | ], 181 | "outputs": { 182 | "hostname": { 183 | "type": "string", 184 | "value": "[reference(variables('publicIPAddressName')).dnsSettings.fqdn]" 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /Tests/AzureTesting/VirtualMachine/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "adminUsername": { 6 | "value": "Test" 7 | }, 8 | "adminPassword": { 9 | "value": "Welkom123" 10 | }, 11 | "dnsLabelPrefix": { 12 | "value": "eandomrlkajelnadada" 13 | }, 14 | "windowsOSVersion": { 15 | "value": "2016-Datacenter" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Tests/AzureTesting/pipelinetest.ps1: -------------------------------------------------------------------------------- 1 | $projectRoot = Resolve-Path "$PSScriptRoot\..\.." 2 | $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") 3 | $moduleName = Split-Path $moduleRoot -Leaf 4 | if (Get-Module ARMHelper) { 5 | Remove-Module -Name ArmHelper -Force 6 | } 7 | 8 | Import-Module (Join-Path $moduleRoot "$moduleName.psd1") -Force 9 | 10 | Describe 'Check Get-ARMDEploymentErrorMessage with Azure' -Tag @("Az") { 11 | InModuleScope ARMHelper { 12 | Context 'Basic functionality' { 13 | It "When a deployment is correct, output is deployment is correct" { 14 | $Parameters = @{ 15 | resourcegroupname = "ArmHelper" 16 | templatefile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.json" 17 | templateparameterfile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.parameters.json" 18 | } 19 | $Result = Get-ARMDeploymentErrorMessage @Parameters 20 | $Result | Should -Be "deployment is correct" 21 | } 22 | 23 | It "Works with a parameterFile" { 24 | $Parameters = @{ 25 | resourcegroupname = "ArmHelper" 26 | templatefile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.json" 27 | templateparameterfile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.parameters.json" 28 | } 29 | $Result = Get-ARMDeploymentErrorMessage @Parameters 30 | $Result | Should -Be "deployment is correct" 31 | } 32 | It "works with a parameter object" { 33 | $Parameterobject = @{ 34 | storageAccountPrefix = "armsta" 35 | storageAccountType = "Standard_LRS" 36 | } 37 | $Parameters = @{ 38 | resourcegroupname = "ArmHelper" 39 | templatefile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.json" 40 | templateparameterobject = $Parameterobject 41 | } 42 | $Result = Get-ARMDeploymentErrorMessage @Parameters 43 | $Result | Should -Be "deployment is correct" 44 | } 45 | It "works with added Parameters" { 46 | $Parameters = @{ 47 | resourcegroupname = "ArmHelper" 48 | templatefile = "$PSScriptRoot\StorageAccountFixed\azuredeploy.json" 49 | storageAccountPrefix = "armsta" 50 | storageAccountType = "Standard_LRS" 51 | } 52 | $Result = Get-ARMDeploymentErrorMessage @Parameters 53 | $Result | Should -Be "deployment is correct" 54 | } 55 | 56 | It "When deployment has a regular error, it is given" { 57 | $Parameters = @{ 58 | resourcegroupname = "ArmHelper" 59 | templatefile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.json" 60 | templateparameterfile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.parameters.json" 61 | } 62 | $Test = Get-ARMDeploymentErrorMessage @Parameters 63 | $Test[0] | Should -Be "Error, Find info below:" 64 | $Test[1] | Should -Be "Deployment template validation failed: 'The template resource '[variables('storageAccountName')]' at line '32' and column '9' is not valid. The type property is invalid. Please see https://aka.ms/arm-template/#resources for usage details.'." 65 | } 66 | # It "When deployment has a general error, the right results are given" { 67 | # $Parameters = @{ 68 | # resourcegroupname = "ArmHelper" 69 | # templatefile = "$PSScriptRoot\StorageAccountGE\azuredeploy.json" 70 | # templateparameterfile = "$PSScriptRoot\StorageAccountGE\azuredeploy.parameters.json" 71 | # } 72 | # $Result = Get-ARMDeploymentErrorMessage @Parameters 73 | # $Result[0] | Should -Be "the output is a generic error message. The log is searched for a more clear errormessage" 74 | # $Result[-3] | Should -Be "General Error. Find info below:" 75 | # $Result[-2] | Should -Be "ErrorCode: AccountNameInvalid" 76 | # $Result[-1] | Should -BeLike "* is not a valid storage account name. Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only." 77 | # } 78 | 79 | Start-Sleep 5 80 | It "Throws when TrowonError is used" { 81 | $Parameters = @{ 82 | resourcegroupname = "ArmHelper" 83 | templatefile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.json" 84 | templateparameterfile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.parameters.json" 85 | } 86 | { Get-ARMDeploymentErrorMessage @Parameters -ThrowOnError } | Should -Throw "Deployment is incorrect" 87 | } 88 | # It "When no errormessage is found in azurelog, script throws" { 89 | # Mock Start-Sleep { $null } 90 | # function Get-AzureRMLog([String]$Name, [Object]$Value, [Switch]$Clobber) { } 91 | # function Get-AzLog([String]$Name, [Object]$Value, [Switch]$Clobber) { } 92 | # Mock Get-AzureRMLog { $null } 93 | # Mock Get-AzLog { $null } 94 | # $Parameters = @{ 95 | # resourcegroupname = "ArmHelper" 96 | # templatefile = "$PSScriptRoot\StorageAccountGE\azuredeploy.json" 97 | # templateparameterfile = "$PSScriptRoot\StorageAccountGE\azuredeploy.parameters.json" 98 | # } 99 | # { Get-ARMDeploymentErrorMessage @Parameters } | Should -Throw "Can't get Azure Log Entry. Please check the log manually in the portal." 100 | # } 101 | 102 | } 103 | } 104 | } 105 | 106 | $projectRoot = Resolve-Path "$PSScriptRoot\..\.." 107 | $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") 108 | $moduleName = Split-Path $moduleRoot -Leaf 109 | if (Get-Module ARMHelper) { 110 | Remove-Module -Name ARMHelper -Force 111 | } 112 | Import-Module (Join-Path $moduleRoot "$moduleName.psd1") -Force 113 | 114 | 115 | Describe 'Check Test-ARMDeploymentResource with Azure' -Tag @("Az") { 116 | InModuleScope ARMHelper { 117 | $Parameters = @{ 118 | resourcegroupname = "ArmHelper" 119 | templatefile = "$PSScriptRoot\VirtualMachine\azuredeploy.json" 120 | templateparameterfile = "$PSScriptRoot\VirtualMachine\azuredeploy.parameters.json" 121 | } 122 | Context 'Basic functionallity' { 123 | 124 | it "Gives a warning for using Az-module"{ 125 | $Result = (Test-ARMDeploymentResource @Parameters 3>&1) 126 | if ($Module -eq "Az"){ 127 | $Result.Message[0] | Should -BeLike "The AZ-module is used. This limits the results of this cmdlet*" 128 | } 129 | if ($Module -eq "AzureRM"){ 130 | $Result.Message[0] | Should -Not -BeLike "The AZ-module is used. This limits the results of this cmdlet*" 131 | } 132 | } 133 | It "Works with a parameterFile" { 134 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 135 | } 136 | It "works with a parameter object" { 137 | $Parameterobject = @{ 138 | adminUsername = "Test" 139 | adminPassword = "Welkom123" 140 | dnsLabelPrefix = "eandomrlkajelnadada" 141 | } 142 | $Parameters = @{ 143 | resourcegroupname = "ArmHelper" 144 | templatefile = "$PSScriptRoot\VirtualMachine\azuredeploy.json" 145 | templateparameterobject = $Parameterobject 146 | } 147 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 148 | 149 | } 150 | It "works with added Parameters" { 151 | $Parameters = @{ 152 | resourcegroupname = "ArmHelper" 153 | templatefile = "$PSScriptRoot\VirtualMachine\azuredeploy.json" 154 | adminPassword = ( "VeryNicePassword" | ConvertTo-SecureString -AsPlainText -force ) 155 | adminUsername = "Test" 156 | dnsLabelPrefix = "eandomrlkajelnadada" 157 | } 158 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 159 | } 160 | It "When a deployment is correct, script doesn't throw" { 161 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 162 | } 163 | It "Shows standard properties for resources that would be in the deployment" { 164 | $Result = Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue 165 | if ($Module -eq "AzureRM"){ 166 | $Result[0].Resource | Should -be "StorageAccounts" 167 | $Result[0].Name | Should -be "mstaqkc32c2qmmw" 168 | $Result[0].Type | Should -be "Microsoft.Storage/storageAccounts" 169 | $Result[0].Location | Should -be "westeurope" 170 | $Result[0].mode | Should -be "Incremental" 171 | $Result[0].ID | Should -BeLike "*/resourceGroups/ArmHelper/providers/Microsoft.Storage/storageAccounts/mstaqkc32c2qmmw" 172 | $Result[0].kind | Should -be "Storage" 173 | $Result[0].sku.name | Should -BeNullOrEmpty 174 | } 175 | if ($Module -eq "Az"){ 176 | $Result[0].Resource | Should -be "StorageAccounts" 177 | $Result[0].Name | Should -be "mstaqkc32c2qmmw" 178 | $Result[0].Type | Should -be "Microsoft.Storage/storageAccounts" 179 | $Result[0].ID | Should -BeLike "*/resourceGroups/ArmHelper/providers/Microsoft.Storage/storageAccounts/mstaqkc32c2qmmw" 180 | 181 | } 182 | } 183 | if ($Module -eq "AzureRM"){ 184 | It "Shows all properties when using Select-Object *" { 185 | if ($Module -eq "AzureRM"){ 186 | $Result = Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue | Select-Object * 187 | $result[0].'.sku.name' | Should -be "Standard_LRS" 188 | $Result[0].apiVersion | Should -be "2018-11-01" 189 | 190 | } 191 | it "When a parameter is a securestring, it is shown as a securestring in the output" { 192 | 193 | $Result = Test-ARMDeploymentResource -WarningAction SilentlyContinue @Parameters 194 | $Result[-1].adminPassword | Should -Be "System.Security.SecureString" 195 | } 196 | } 197 | } 198 | it "Should throw when no result is found" { 199 | $Parameters = @{ 200 | resourcegroupname = "ArmHelper" 201 | templatefile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.json" 202 | templateparameterfile = "$PSScriptRoot\StorageAccountBroken\azuredeploy.parameters.json" 203 | } 204 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should throw "Something is wrong with the output, no resources found. Please check your deployment with Get-ARMdeploymentErrorMessage" 205 | } 206 | } 207 | } 208 | } -------------------------------------------------------------------------------- /Tests/Basic_Module.tests.ps1: -------------------------------------------------------------------------------- 1 | # Copied from https://powershellexplained.com/2017-01-21-powershell-module-continious-delivery-pipeline/ 2 | 3 | $projectRoot = Resolve-Path "$PSScriptRoot\.." 4 | $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") 5 | $moduleName = Split-Path $moduleRoot -Leaf 6 | 7 | 8 | Describe "General project validation: $moduleName" { 9 | 10 | $scripts = Get-ChildItem $projectRoot -Include *.ps1, *.psm1, *.psd1 -Recurse 11 | 12 | # TestCases are splatted to the script so we need hashtables 13 | $testCase = $scripts | Foreach-Object { 14 | @{ 15 | file = $_ 16 | } 17 | } 18 | 19 | It "Script should exist" -TestCases $testCase { 20 | param($file) 21 | 22 | $file.fullname | Should -Exist 23 | } 24 | 25 | It "Script can be read without errors" -TestCases $testCase { 26 | param($file) 27 | 28 | { $null = Get-Content -Path $file.fullname -ErrorAction Stop } | Should -Not -Throw 29 | } 30 | 31 | It "Script should be valid powershell" -TestCases $testCase { 32 | param($file) 33 | 34 | $contents = Get-Content -Path $file.fullname -ErrorAction SilentlyContinue 35 | $errors = $null 36 | $null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors) 37 | $errors.Count | Should -Be 0 38 | } 39 | 40 | It "Module '$moduleName' can import cleanly" { 41 | { Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force } | Should -Not -Throw 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/Get-ARMDeploymentErrorMessage.tests.ps1: -------------------------------------------------------------------------------- 1 | $projectRoot = Resolve-Path "$PSScriptRoot\.." 2 | $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") 3 | $moduleName = Split-Path $moduleRoot -Leaf 4 | 5 | Import-Module (Join-Path $moduleRoot "$moduleName.psd1") -force 6 | 7 | 8 | 9 | Describe 'Check Get-ARMDEploymentErrorMessage' -Tag @("Mock") { 10 | InModuleScope ARMHelper { 11 | $Parameters = @{ 12 | resourcegroupname = "Arm" 13 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 14 | templateparameterfile = ".\azuredeploy.parameters.json" 15 | } 16 | Context 'Basic functionality AzureRM' { 17 | function Test-AzureRMResourceGroupDeployment([String]$Name, [Object]$Value, [Switch]$Clobber) { } 18 | 19 | function Get-AzureRMLog([String]$Name, [Object]$Value, [Switch]$Clobber) { } 20 | Mock Test-ARMAzureModule { "AzureRM" } 21 | It "Works with a parameterFile" { 22 | Mock Test-AzureRmResourceGroupDeployment -parameterfilter { $Parameters } { $null } 23 | $Result = Get-ARMDeploymentErrorMessage @Parameters 24 | $Result | Should -Be "deployment is correct" 25 | } 26 | It "works with a parameter object" { 27 | $Parameterobject = @{ 28 | storageAccountPrefix = "armsta" 29 | storageAccountType = "LRS" 30 | } 31 | $Parameters = @{ 32 | resourcegroupname = "Arm" 33 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 34 | templateparameterobject = $Parameterobject 35 | } 36 | Mock Test-AzureRmResourceGroupDeployment -parameterfilter { $Parameters } { $null } 37 | $Result = Get-ARMDeploymentErrorMessage @Parameters 38 | $Result | Should -Be "deployment is correct" 39 | } 40 | It "works with added Parameters" { 41 | $Parameters = @{ 42 | resourcegroupname = "Arm" 43 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 44 | storageAccountPrefix = "armsta" 45 | storageAccountType = "LRS" 46 | } 47 | Mock Test-AzureRmResourceGroupDeployment -parameterfilter { $Parameters } { $null } 48 | $Result = Get-ARMDeploymentErrorMessage @Parameters 49 | $Result | Should -Be "deployment is correct" 50 | } 51 | It "When a deployment is correct, output is deployment is correct" { 52 | Mock Test-AzureRmResourceGroupDeployment -parameterfilter { $Parameters } { $null } 53 | $Result = Get-ARMDeploymentErrorMessage @Parameters 54 | $Result | Should -Be "deployment is correct" 55 | } 56 | It "When deployment has a regular error, it is given" { 57 | Mock Test-AzureRmResourceGroupDeployment -parameterfilter { $Parameters } { 58 | [pscustomobject]@{ 59 | Code = 'InvalidTemplateDeployment' 60 | Message = "Deployment template validation failed" 61 | } 62 | } 63 | $Test = Get-ARMDeploymentErrorMessage @Parameters 64 | $Test[0] | Should -Be "Error, Find info below:" 65 | $Test[1] | Should -Be "Deployment template validation failed" 66 | } 67 | It "When deployment has a general error, the right results are given" { 68 | Mock Test-AzureRmResourceGroupDeployment -parameterfilter { $Parameters } { 69 | [pscustomobject]@{ 70 | Code = 'InvalidTemplateDeployment' 71 | Message = "The template deployment '12345678-1234-1234-1234-12345678abcd' is not valid according to the validation procedure. The 72 | tracking id is '12345678-1234-1234-1234-12345678abcd'. See inner errors for details. Please see https://aka.ms/arm-deploy 73 | for usage details." 74 | } 75 | } 76 | $Mockobject = (Get-Content $PSScriptRoot\MockObjects\Logoutput.json) | ConvertFrom-Json 77 | Mock Get-AzureRMLog { 78 | [object]$Mockobject 79 | } 80 | Mock Start-Sleep { $null } 81 | $Result = Get-ARMDeploymentErrorMessage @Parameters 82 | $Result[0] | Should -Be "the output is a generic error message. The log is searched for a more clear errormessage" 83 | $Result[-3] | Should -Be "General Error. Find info below:" 84 | $Result[-2] | Should -Be "ErrorCode: AccountNameInvalid" 85 | $Result[-1] | Should -Be "Errormessage: s aqkc32cvb2qmmw is not a valid storage account name. Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only." 86 | } 87 | It "When no errormessage is found in azurelog, script throws" { 88 | Mock Test-AzureRmResourceGroupDeployment -parameterfilter { $Parameters } { 89 | [pscustomobject]@{ 90 | Code = 'InvalidTemplateDeployment' 91 | Message = "The template deployment '12345678-1234-1234-1234-12345678abcd' is not valid according to the validation procedure. The 92 | tracking id is '12345678-1234-1234-1234-12345678abcd'. See inner errors for details. Please see https://aka.ms/arm-deploy 93 | for usage details." 94 | } 95 | } 96 | Mock Get-AzureRMLog { 97 | $null 98 | } 99 | Mock Start-Sleep { $null } 100 | { Get-ARMDeploymentErrorMessage @Parameters } | Should -Throw "Can't get Azure Log Entry. Please check the log manually in the portal." 101 | } 102 | It "Throws when TrowonError is used" { 103 | Mock Test-AzureRmResourceGroupDeployment -parameterfilter { $Parameters } { 104 | [pscustomobject]@{ 105 | Code = 'InvalidTemplateDeployment' 106 | Message = "The template deployment '12345678-1234-1234-1234-12345678abcd' is not valid according to the validation procedure. The 107 | tracking id is '12345678-1234-1234-1234-12345678abcd'. See inner errors for details. Please see https://aka.ms/arm-deploy 108 | for usage details." 109 | } 110 | } 111 | $Mockobject = (Get-Content $PSScriptRoot\MockObjects\Logoutput.json) | ConvertFrom-Json 112 | Mock Get-AzureRMLog { 113 | [object]$Mockobject 114 | } 115 | Mock Start-Sleep { $null } 116 | { Get-ARMDeploymentErrorMessage @Parameters -ThrowOnError } | Should -Throw "Deployment is incorrect" 117 | 118 | } 119 | It "All Mocks are called" { 120 | Assert-MockCalled -CommandName Get-AzureRmLog 121 | Assert-MockCalled -CommandName Test-AzureRmResourceGroupDeployment 122 | } 123 | } 124 | Context 'Basic functionality Az' { 125 | function Test-AzResourceGroupDeployment([String]$Name, [Object]$Value, [Switch]$Clobber) { } 126 | function Get-AzLog([String]$Name, [Object]$Value, [Switch]$Clobber) { } 127 | 128 | Mock Test-ARMAzureModule { "Az" } 129 | It "Works with a parameterFile" { 130 | Mock Test-AzResourceGroupDeployment -parameterfilter { $Parameters } { $null } 131 | $Result = Get-ARMDeploymentErrorMessage @Parameters 132 | $Result | Should -Be "deployment is correct" 133 | } 134 | It "works with a parameter object" { 135 | $Parameterobject = @{ 136 | storageAccountPrefix = "armsta" 137 | storageAccountType = "LRS" 138 | } 139 | $Parameters = @{ 140 | resourcegroupname = "Arm" 141 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 142 | templateparameterobject = $Parameterobject 143 | } 144 | Mock Test-AzResourceGroupDeployment -parameterfilter { $Parameters } { $null } 145 | $Result = Get-ARMDeploymentErrorMessage @Parameters 146 | $Result | Should -Be "deployment is correct" 147 | } 148 | It "works with added Parameters" { 149 | $Parameters = @{ 150 | resourcegroupname = "Arm" 151 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 152 | storageAccountPrefix = "armsta" 153 | storageAccountType = "LRS" 154 | } 155 | Mock Test-AzResourceGroupDeployment -parameterfilter { $Parameters } { $null } 156 | $Result = Get-ARMDeploymentErrorMessage @Parameters 157 | $Result | Should -Be "deployment is correct" 158 | } 159 | It "When a deployment is correct, output is deployment is correct" { 160 | Mock Test-AzResourceGroupDeployment -parameterfilter { $Parameters } { $null } 161 | $Result = Get-ARMDeploymentErrorMessage @Parameters 162 | $Result | Should -Be "deployment is correct" 163 | } 164 | It "When deployment has a regular error, it is given" { 165 | Mock Test-AzResourceGroupDeployment -parameterfilter { $Parameters } { 166 | [pscustomobject]@{ 167 | Code = 'InvalidTemplateDeployment' 168 | Message = "Deployment template validation failed" 169 | } 170 | } 171 | $Test = Get-ARMDeploymentErrorMessage @Parameters 172 | $Test[0] | Should -Be "Error, Find info below:" 173 | $Test[1] | Should -Be "Deployment template validation failed" 174 | } 175 | It "When deployment has a general error, the right results are given" { 176 | Mock Test-AzResourceGroupDeployment -parameterfilter { $Parameters } { 177 | [pscustomobject]@{ 178 | Code = 'InvalidTemplateDeployment' 179 | Message = "The template deployment '12345678-1234-1234-1234-12345678abcd' is not valid according to the validation procedure. The 180 | tracking id is '12345678-1234-1234-1234-12345678abcd'. See inner errors for details. Please see https://aka.ms/arm-deploy 181 | for usage details." 182 | } 183 | } 184 | $Mockobject = (Get-Content $PSScriptRoot\MockObjects\Logoutput.json) | ConvertFrom-Json 185 | Mock Get-AzLog { 186 | [object]$Mockobject 187 | } 188 | Mock Start-Sleep { $null } 189 | $Result = Get-ARMDeploymentErrorMessage @Parameters 190 | $Result[0] | Should -Be "the output is a generic error message. The log is searched for a more clear errormessage" 191 | $Result[-3] | Should -Be "General Error. Find info below:" 192 | $Result[-2] | Should -Be "ErrorCode: AccountNameInvalid" 193 | $Result[-1] | Should -Be "Errormessage: s aqkc32cvb2qmmw is not a valid storage account name. Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only." 194 | } 195 | It "When no errormessage is found in azurelog, script throws" { 196 | Mock Test-AzResourceGroupDeployment -parameterfilter { $Parameters } { 197 | [pscustomobject]@{ 198 | Code = 'InvalidTemplateDeployment' 199 | Message = "The template deployment '12345678-1234-1234-1234-12345678abcd' is not valid according to the validation procedure. The 200 | tracking id is '12345678-1234-1234-1234-12345678abcd'. See inner errors for details. Please see https://aka.ms/arm-deploy 201 | for usage details." 202 | } 203 | } 204 | Mock Get-AzLog { 205 | $null 206 | } 207 | Mock Start-Sleep { $null } 208 | { Get-ARMDeploymentErrorMessage @Parameters } | Should -Throw "Can't get Azure Log Entry. Please check the log manually in the portal." 209 | } 210 | It "Throws when TrowonError is used" { 211 | Mock Test-AzResourceGroupDeployment -parameterfilter { $Parameters } { 212 | [pscustomobject]@{ 213 | Code = 'InvalidTemplateDeployment' 214 | Message = "The template deployment '12345678-1234-1234-1234-12345678abcd' is not valid according to the validation procedure. The 215 | tracking id is '12345678-1234-1234-1234-12345678abcd'. See inner errors for details. Please see https://aka.ms/arm-deploy 216 | for usage details." 217 | } 218 | } 219 | $Mockobject = (Get-Content $PSScriptRoot\MockObjects\Logoutput.json) | ConvertFrom-Json 220 | Mock Get-AzLog { 221 | [object]$Mockobject 222 | } 223 | Mock Start-Sleep { $null } 224 | { Get-ARMDeploymentErrorMessage @Parameters -ThrowOnError } | Should -Throw "Deployment is incorrect" 225 | 226 | } 227 | It "All Mocks are called" { 228 | Assert-MockCalled -CommandName Get-AzLog 229 | Assert-MockCalled -CommandName Test-AzResourceGroupDeployment 230 | } 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Tests/GetHelp.tests.ps1: -------------------------------------------------------------------------------- 1 | $projectRoot = Resolve-Path "$PSScriptRoot\.." 2 | $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") 3 | $moduleName = Split-Path $moduleRoot -Leaf 4 | 5 | Import-Module (Join-Path $moduleRoot "$moduleName.psd1") -force 6 | 7 | Describe 'Check Comment-based help' { 8 | Context 'All functions should contain Comment-based Help' { 9 | $Commands = (Get-Module ARMHelper).ExportedCommands 10 | 11 | $TestCases = $Commands.Values | Foreach-Object { 12 | @{ 13 | Function = $_.Name 14 | } 15 | } 16 | 17 | It " should contain Comment-based Help - Synopsis" -TestCases $TestCases { 18 | param( 19 | $Function 20 | ) 21 | 22 | $Help = Get-Help $Function 23 | 24 | $Help.Synopsis | Should -Not -BeNullOrEmpty 25 | } 26 | 27 | It " should contain Comment-based Help - Description" -TestCases $TestCases { 28 | param( 29 | $Function 30 | ) 31 | 32 | $Help = Get-Help $Function 33 | 34 | $Help.Description | Should -Not -BeNullOrEmpty 35 | } 36 | 37 | It " should contain Comment-based Help - Synopsis" -TestCases $TestCases { 38 | param( 39 | $Function 40 | ) 41 | 42 | $Examples = Get-Help $Function -Examples 43 | 44 | $Examples | Should -Not -BeNullOrEmpty 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/MockObjects/ExistingResources.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Compute/disks/SimpleWinVM_disk2_ee1400356e2f458c973b7d7dcecd9b17", 4 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Compute/disks/SimpleWinVM_disk2_ee1400356e2f458c973b7d7dcecd9b17", 5 | "Identity": null, 6 | "Kind": null, 7 | "Location": "westeurope", 8 | "ManagedBy": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 9 | "ResourceName": "SimpleWinVM_disk2_ee1400356e2f458c973b7d7dcecd9b17", 10 | "Name": "SimpleWinVM_disk2_ee1400356e2f458c973b7d7dcecd9b17", 11 | "ExtensionResourceName": null, 12 | "ParentResource": null, 13 | "Plan": null, 14 | "Properties": null, 15 | "ResourceGroupName": "Arm", 16 | "Type": "Microsoft.Compute/disks", 17 | "ResourceType": "Microsoft.Compute/disks", 18 | "ExtensionResourceType": null, 19 | "Sku": { 20 | "Name": "Standard_LRS", 21 | "Tier": "Standard", 22 | "Size": null, 23 | "Family": null, 24 | "Model": null, 25 | "Capacity": null 26 | }, 27 | "Tags": null, 28 | "SubscriptionId": null, 29 | "CreatedTime": null, 30 | "ChangedTime": null, 31 | "ETag": null 32 | }, 33 | { 34 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Compute/disks/SimpleWinVM_OsDisk_1_f602a12020aa445682f68e35060433da", 35 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Compute/disks/SimpleWinVM_OsDisk_1_f602a12020aa445682f68e35060433da", 36 | "Identity": null, 37 | "Kind": null, 38 | "Location": "westeurope", 39 | "ManagedBy": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 40 | "ResourceName": "SimpleWinVM_OsDisk_1_f602a12020aa445682f68e35060433da", 41 | "Name": "SimpleWinVM_OsDisk_1_f602a12020aa445682f68e35060433da", 42 | "ExtensionResourceName": null, 43 | "ParentResource": null, 44 | "Plan": null, 45 | "Properties": null, 46 | "ResourceGroupName": "Arm", 47 | "Type": "Microsoft.Compute/disks", 48 | "ResourceType": "Microsoft.Compute/disks", 49 | "ExtensionResourceType": null, 50 | "Sku": { 51 | "Name": "Standard_LRS", 52 | "Tier": "Standard", 53 | "Size": null, 54 | "Family": null, 55 | "Model": null, 56 | "Capacity": null 57 | }, 58 | "Tags": null, 59 | "SubscriptionId": null, 60 | "CreatedTime": null, 61 | "ChangedTime": null, 62 | "ETag": null 63 | }, 64 | { 65 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 66 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 67 | "Identity": null, 68 | "Kind": null, 69 | "Location": "westeurope", 70 | "ManagedBy": null, 71 | "ResourceName": "SimpleWinVM", 72 | "Name": "SimpleWinVM", 73 | "ExtensionResourceName": null, 74 | "ParentResource": null, 75 | "Plan": null, 76 | "Properties": null, 77 | "ResourceGroupName": "Arm", 78 | "Type": "Microsoft.Compute/virtualMachines", 79 | "ResourceType": "Microsoft.Compute/virtualMachines", 80 | "ExtensionResourceType": null, 81 | "Sku": null, 82 | "Tags": null, 83 | "SubscriptionId": null, 84 | "CreatedTime": null, 85 | "ChangedTime": null, 86 | "ETag": null 87 | }, 88 | { 89 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Network/networkInterfaces/myVMNic", 90 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Network/networkInterfaces/myVMNic", 91 | "Identity": null, 92 | "Kind": null, 93 | "Location": "westeurope", 94 | "ManagedBy": null, 95 | "ResourceName": "myVMNic", 96 | "Name": "myVMNic", 97 | "ExtensionResourceName": null, 98 | "ParentResource": null, 99 | "Plan": null, 100 | "Properties": null, 101 | "ResourceGroupName": "Arm", 102 | "Type": "Microsoft.Network/networkInterfaces", 103 | "ResourceType": "Microsoft.Network/networkInterfaces", 104 | "ExtensionResourceType": null, 105 | "Sku": null, 106 | "Tags": null, 107 | "SubscriptionId": null, 108 | "CreatedTime": null, 109 | "ChangedTime": null, 110 | "ETag": null 111 | }, 112 | { 113 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Network/publicIPAddresses/myPublicIP", 114 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Network/publicIPAddresses/myPublicIP", 115 | "Identity": null, 116 | "Kind": null, 117 | "Location": "westeurope", 118 | "ManagedBy": null, 119 | "ResourceName": "myPublicIP", 120 | "Name": "myPublicIP", 121 | "ExtensionResourceName": null, 122 | "ParentResource": null, 123 | "Plan": null, 124 | "Properties": null, 125 | "ResourceGroupName": "Arm", 126 | "Type": "Microsoft.Network/publicIPAddresses", 127 | "ResourceType": "Microsoft.Network/publicIPAddresses", 128 | "ExtensionResourceType": null, 129 | "Sku": { 130 | "Name": "Basic", 131 | "Tier": "Regional", 132 | "Size": null, 133 | "Family": null, 134 | "Model": null, 135 | "Capacity": null 136 | }, 137 | "Tags": null, 138 | "SubscriptionId": null, 139 | "CreatedTime": null, 140 | "ChangedTime": null, 141 | "ETag": null 142 | }, 143 | { 144 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Network/virtualNetworks/MyVNET", 145 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Network/virtualNetworks/MyVNET", 146 | "Identity": null, 147 | "Kind": null, 148 | "Location": "westeurope", 149 | "ManagedBy": null, 150 | "ResourceName": "MyVNET", 151 | "Name": "MyVNET", 152 | "ExtensionResourceName": null, 153 | "ParentResource": null, 154 | "Plan": null, 155 | "Properties": null, 156 | "ResourceGroupName": "Arm", 157 | "Type": "Microsoft.Network/virtualNetworks", 158 | "ResourceType": "Microsoft.Network/virtualNetworks", 159 | "ExtensionResourceType": null, 160 | "Sku": null, 161 | "Tags": null, 162 | "SubscriptionId": null, 163 | "CreatedTime": null, 164 | "ChangedTime": null, 165 | "ETag": null 166 | }, 167 | { 168 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Storage/storageAccounts/mstaqkc32c2qmmw", 169 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Arm/providers/Microsoft.Storage/storageAccounts/mstaqkc32c2qmmw", 170 | "Identity": null, 171 | "Kind": "Storage", 172 | "Location": "westeurope", 173 | "ManagedBy": null, 174 | "ResourceName": "mstaqkc32c2qmmw", 175 | "Name": "mstaqkc32c2qmmw", 176 | "ExtensionResourceName": null, 177 | "ParentResource": null, 178 | "Plan": null, 179 | "Properties": null, 180 | "ResourceGroupName": "Arm", 181 | "Type": "Microsoft.Storage/storageAccounts", 182 | "ResourceType": "Microsoft.Storage/storageAccounts", 183 | "ExtensionResourceType": null, 184 | "Sku": { 185 | "Name": "Standard_LRS", 186 | "Tier": "Standard", 187 | "Size": null, 188 | "Family": null, 189 | "Model": null, 190 | "Capacity": null 191 | }, 192 | "Tags": {}, 193 | "SubscriptionId": null, 194 | "CreatedTime": null, 195 | "ChangedTime": null, 196 | "ETag": null 197 | } 198 | ] 199 | -------------------------------------------------------------------------------- /Tests/MockObjects/ExistingResourcesDeleted.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Storage/storageAccounts/testaegewteawgfaef", 4 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Storage/storageAccounts/testaegewteawgfaef", 5 | "Identity": null, 6 | "Kind": "StorageV2", 7 | "Location": "westeurope", 8 | "ManagedBy": null, 9 | "ResourceName": "testaegewteawgfaef", 10 | "Name": "testaegewteawgfaef", 11 | "ExtensionResourceName": null, 12 | "ParentResource": null, 13 | "Plan": null, 14 | "Properties": null, 15 | "ResourceGroupName": "ARM", 16 | "Type": "Microsoft.Storage/storageAccounts", 17 | "ResourceType": "Microsoft.Storage/storageAccounts", 18 | "ExtensionResourceType": null, 19 | "Sku": { 20 | "Name": "Standard_LRS", 21 | "Tier": "Standard", 22 | "Size": null, 23 | "Family": null, 24 | "Model": null, 25 | "Capacity": null 26 | }, 27 | "Tags": {}, 28 | "SubscriptionId": null, 29 | "CreatedTime": null, 30 | "ChangedTime": null, 31 | "ETag": null 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /Tests/MockObjects/ExistingResourcesdiffRG.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | { 4 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 5 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 6 | "Identity": null, 7 | "Kind": null, 8 | "Location": "westeurope", 9 | "ManagedBy": null, 10 | "ResourceName": "SimpleWinVM", 11 | "Name": "SimpleWinVM", 12 | "ExtensionResourceName": null, 13 | "ParentResource": null, 14 | "Plan": null, 15 | "Properties": null, 16 | "ResourceGroupName": "Armtesting", 17 | "Type": "Microsoft.Compute/virtualMachines", 18 | "ResourceType": "Microsoft.Compute/virtualMachines", 19 | "ExtensionResourceType": null, 20 | "Sku": null, 21 | "Tags": null, 22 | "SubscriptionId": null, 23 | "CreatedTime": null, 24 | "ChangedTime": null, 25 | "ETag": null 26 | }, 27 | { 28 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 29 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 30 | "Identity": null, 31 | "Kind": null, 32 | "Location": "westeurope", 33 | "ManagedBy": null, 34 | "ResourceName": "myVMNic", 35 | "Name": "myVMNic", 36 | "ExtensionResourceName": null, 37 | "ParentResource": null, 38 | "Plan": null, 39 | "Properties": null, 40 | "ResourceGroupName": "Armtesting", 41 | "Type": "Microsoft.Network/networkInterfaces", 42 | "ResourceType": "Microsoft.Network/networkInterfaces", 43 | "ExtensionResourceType": null, 44 | "Sku": null, 45 | "Tags": null, 46 | "SubscriptionId": null, 47 | "CreatedTime": null, 48 | "ChangedTime": null, 49 | "ETag": null 50 | }, 51 | { 52 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP", 53 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP", 54 | "Identity": null, 55 | "Kind": null, 56 | "Location": "westeurope", 57 | "ManagedBy": null, 58 | "ResourceName": "myPublicIP", 59 | "Name": "myPublicIP", 60 | "ExtensionResourceName": null, 61 | "ParentResource": null, 62 | "Plan": null, 63 | "Properties": null, 64 | "ResourceGroupName": "Armtesting", 65 | "Type": "Microsoft.Network/publicIPAddresses", 66 | "ResourceType": "Microsoft.Network/publicIPAddresses", 67 | "ExtensionResourceType": null, 68 | "Sku": { 69 | "Name": "Basic", 70 | "Tier": "Regional", 71 | "Size": null, 72 | "Family": null, 73 | "Model": null, 74 | "Capacity": null 75 | }, 76 | "Tags": null, 77 | "SubscriptionId": null, 78 | "CreatedTime": null, 79 | "ChangedTime": null, 80 | "ETag": null 81 | }, 82 | { 83 | "ResourceId": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Network/virtualNetworks/MyVNET", 84 | "Id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Network/virtualNetworks/MyVNET", 85 | "Identity": null, 86 | "Kind": null, 87 | "Location": "westeurope", 88 | "ManagedBy": null, 89 | "ResourceName": "MyVNET", 90 | "Name": "MyVNET", 91 | "ExtensionResourceName": null, 92 | "ParentResource": null, 93 | "Plan": null, 94 | "Properties": null, 95 | "ResourceGroupName": "Armtesting", 96 | "Type": "Microsoft.Network/virtualNetworks", 97 | "ResourceType": "Microsoft.Network/virtualNetworks", 98 | "ExtensionResourceType": null, 99 | "Sku": null, 100 | "Tags": null, 101 | "SubscriptionId": null, 102 | "CreatedTime": null, 103 | "ChangedTime": null, 104 | "ETag": null 105 | }, 106 | 107 | ] 108 | -------------------------------------------------------------------------------- /Tests/MockObjects/Logoutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "Properties": { 3 | "Content": { 4 | "statusCode": "BadRequest", 5 | "serviceRequestId": null, 6 | "statusMessage": "{\"error\":{\"code\":\"InvalidTemplateDeployment\",\"message\":\"The template deployment 'f0f1ac32-fdc6-44ba-90c7-2aed1a84388e' is not valid according to the validation procedure. The tracking id is 'a9ec07b0-86be-429b-b8a5-c5b2dd32d003'. See inner errors for details. Please see https://aka.ms/arm-deploy for usage details.\",\"details\":[{\"code\":\"PreflightValidationCheckFailed\",\"message\":\"Preflight validation failed. Please refer to the details for the specific errors.\",\"details\":[{\"code\":\"AccountNameInvalid\",\"target\":\"s aqkc32cvb2qmmw\",\"message\":\"s aqkc32cvb2qmmw is not a valid storage account name. Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only.\"}]}]}}" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /Tests/MockObjects/NestedResult.json: -------------------------------------------------------------------------------- 1 | { 2 | "templateHash": "12342355", 3 | "parameters": { 4 | "adminUsername": { 5 | "type": "String", 6 | "value": "henk" 7 | }, 8 | "adminPassword": { 9 | "type": "SecureString" 10 | }, 11 | "dnsLabelPrefix": { 12 | "type": "String", 13 | "value": "b233523523la" 14 | }, 15 | "uri": { 16 | "type": "String", 17 | "value": "https://example.blob.core.windows.net" 18 | }, 19 | "sas": { 20 | "type": "SecureString" 21 | }, 22 | "windowsOSVersion": { 23 | "type": "String", 24 | "value": "2016-Datacenter" 25 | }, 26 | "location": { 27 | "type": "String", 28 | "value": "westeurope" 29 | } 30 | }, 31 | "mode": "Complete", 32 | "provisioningState": "Succeeded", 33 | "timestamp": "2019-09-05T16:49:13.4122369Z", 34 | "duration": "PT0S", 35 | "correlationId": "7034ffb6-3b17-4ab6-b289-5899ad46e576", 36 | "providers": [ 37 | { 38 | "namespace": "Microsoft.Resources", 39 | "resourceTypes": "" 40 | }, 41 | { 42 | "namespace": "Microsoft.Network", 43 | "resourceTypes": " " 44 | }, 45 | { 46 | "namespace": "Microsoft.Compute", 47 | "resourceTypes": "" 48 | } 49 | ], 50 | "dependencies": [ 51 | { 52 | "dependsOn": " ", 53 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 54 | "resourceType": "Microsoft.Network/networkInterfaces", 55 | "resourceName": "myVMNic" 56 | }, 57 | { 58 | "dependsOn": " ", 59 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 60 | "resourceType": "Microsoft.Compute/virtualMachines", 61 | "resourceName": "SimpleWinVM" 62 | } 63 | ], 64 | "validatedResources": [ 65 | { 66 | "apiVersion": "2018-05-01", 67 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Resources/Deployments/linkedTemplate", 68 | "name": "linkedTemplate", 69 | "type": "Microsoft.Resources/Deployments", 70 | "properties": "@{mode=Incremental; templateLink=; parameters=}" 71 | }, 72 | { 73 | "apiVersion": "2018-11-01", 74 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP", 75 | "name": "myPublicIP", 76 | "type": "Microsoft.Network/publicIPAddresses", 77 | "location": "westeurope", 78 | "properties": "@{publicIPAllocationMethod=Dynamic; dnsSettings=}" 79 | }, 80 | { 81 | "apiVersion": "2018-11-01", 82 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Network/virtualNetworks/MyVNET", 83 | "name": "MyVNET", 84 | "type": "Microsoft.Network/virtualNetworks", 85 | "location": "westeurope", 86 | "properties": "@{addressSpace=; subnets=System.Object[]}" 87 | }, 88 | { 89 | "dependsOn": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ArmTesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP /subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ArmTesting/providers/Microsoft.Network/virtualNetworks/MyVNET", 90 | "apiVersion": "2018-11-01", 91 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 92 | "name": "myVMNic", 93 | "type": "Microsoft.Network/networkInterfaces", 94 | "location": "westeurope", 95 | "properties": "@{ipConfigurations=System.Object[]}" 96 | }, 97 | { 98 | "dependsOn": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Resources/Deployments/linkedTemplate /subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ArmTesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 99 | "apiVersion": "2018-10-01", 100 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/Armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 101 | "name": "SimpleWinVM", 102 | "type": "Microsoft.Compute/virtualMachines", 103 | "location": "westeurope", 104 | "properties": "@{hardwareProfile=; osProfile=; storageProfile=; networkProfile=; diagnosticsProfile=}" 105 | }, 106 | { 107 | "apiVersion": "2018-11-01", 108 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ArmTesting/providers/Microsoft.Storage/storageAccounts/123", 109 | "name": "ackjvgp3y3huksawinvm", 110 | "type": "Microsoft.Storage/storageAccounts", 111 | "sku": "@{name=Standard_LRS}", 112 | "kind": "Storage", 113 | "location": "westeurope", 114 | "properties": "" 115 | } 116 | ] 117 | } 118 | -------------------------------------------------------------------------------- /Tests/MockObjects/Result.json: -------------------------------------------------------------------------------- 1 | { 2 | "templateHash": "1220693435534564543", 3 | "parameters": { 4 | "adminUsername": { 5 | "type": "String", 6 | "value": "Henk" 7 | }, 8 | "adminPassword": { 9 | "type": "SecureString" 10 | }, 11 | "dnsLabelPrefix": { 12 | "type": "String", 13 | "value": "eandomrlkajelnadada" 14 | }, 15 | "windowsOSVersion": { 16 | "type": "String", 17 | "value": "2016-Datacenter" 18 | }, 19 | "location": { 20 | "type": "String", 21 | "value": "westeurope" 22 | } 23 | }, 24 | "mode": "Incremental", 25 | "provisioningState": "Succeeded", 26 | "timestamp": "2019-07-05T20:45:58.2062472Z", 27 | "duration": "PT0S", 28 | "correlationId": "93690b-4ba0-461b-a5da-dc7b0", 29 | "providers": [ 30 | { 31 | "namespace": "Microsoft.Storage", 32 | "resourceTypes": [ 33 | { 34 | "resourceType": "storageAccounts", 35 | "locations": [ 36 | "westeurope" 37 | ] 38 | } 39 | ] 40 | }, 41 | { 42 | "namespace": "Microsoft.Network", 43 | "resourceTypes": [ 44 | { 45 | "resourceType": "publicIPAddresses", 46 | "locations": [ 47 | "westeurope" 48 | ] 49 | }, 50 | { 51 | "resourceType": "virtualNetworks", 52 | "locations": [ 53 | "westeurope" 54 | ] 55 | }, 56 | { 57 | "resourceType": "networkInterfaces", 58 | "locations": [ 59 | "westeurope" 60 | ] 61 | } 62 | ] 63 | }, 64 | { 65 | "namespace": "Microsoft.Compute", 66 | "resourceTypes": [ 67 | { 68 | "resourceType": "virtualMachines", 69 | "locations": [ 70 | "westeurope" 71 | ] 72 | } 73 | ] 74 | } 75 | ], 76 | "dependencies": [ 77 | { 78 | "dependsOn": [ 79 | { 80 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP", 81 | "resourceType": "Microsoft.Network/publicIPAddresses", 82 | "resourceName": "myPublicIP" 83 | }, 84 | { 85 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/virtualNetworks/MyVNET", 86 | "resourceType": "Microsoft.Network/virtualNetworks", 87 | "resourceName": "MyVNET" 88 | } 89 | ], 90 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 91 | "resourceType": "Microsoft.Network/networkInterfaces", 92 | "resourceName": "myVMNic" 93 | }, 94 | { 95 | "dependsOn": [ 96 | { 97 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Storage/storageAccounts/qkc32cvb2qmmwsawinvm", 98 | "resourceType": "Microsoft.Storage/storageAccounts", 99 | "resourceName": "qkc32cvb2qmmwsawinvm" 100 | }, 101 | { 102 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 103 | "resourceType": "Microsoft.Network/networkInterfaces", 104 | "resourceName": "myVMNic" 105 | } 106 | ], 107 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 108 | "resourceType": "Microsoft.Compute/virtualMachines", 109 | "resourceName": "SimpleWinVM" 110 | } 111 | ], 112 | "validatedResources": [ 113 | { 114 | "apiVersion": "2018-11-01", 115 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Storage/storageAccounts/qkc32cvb2qmmwsawinvm", 116 | "name": "qkc32cvb2qmmwsawinvm", 117 | "type": "Microsoft.Storage/storageAccounts", 118 | "sku": { 119 | "name": "Standard_LRS" 120 | }, 121 | "kind": "Storage", 122 | "location": "westeurope", 123 | "properties": { 124 | 125 | } 126 | }, 127 | { 128 | "apiVersion": "2018-11-01", 129 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP", 130 | "name": "myPublicIP", 131 | "type": "Microsoft.Network/publicIPAddresses", 132 | "location": "westeurope", 133 | "properties": { 134 | "publicIPAllocationMethod": "Dynamic", 135 | "dnsSettings": { 136 | "domainNameLabel": "eandomrlkajelnadada" 137 | } 138 | } 139 | }, 140 | { 141 | "apiVersion": "2018-11-01", 142 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/virtualNetworks/MyVNET", 143 | "name": "MyVNET", 144 | "type": "Microsoft.Network/virtualNetworks", 145 | "location": "westeurope", 146 | "properties": { 147 | "addressSpace": { 148 | "addressPrefixes": [ 149 | "10.0.0.0/16" 150 | ] 151 | }, 152 | "subnets": [ 153 | { 154 | "name": "Subnet", 155 | "properties": { 156 | "addressPrefix": "10.0.0.0/24" 157 | } 158 | } 159 | ] 160 | } 161 | }, 162 | { 163 | "dependsOn": [ 164 | "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP", 165 | "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/virtualNetworks/MyVNET" 166 | ], 167 | "apiVersion": "2018-11-01", 168 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 169 | "name": "myVMNic", 170 | "type": "Microsoft.Network/networkInterfaces", 171 | "location": "westeurope", 172 | "properties": { 173 | "ipConfigurations": [ 174 | { 175 | "name": "ipconfig1", 176 | "properties": { 177 | "privateIPAllocationMethod": "Dynamic", 178 | "publicIPAddress": { 179 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP" 180 | }, 181 | "subnet": { 182 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/virtualNetworks/MyVNET/subnets/Subnet" 183 | } 184 | } 185 | } 186 | ] 187 | } 188 | }, 189 | { 190 | "dependsOn": [ 191 | "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Storage/storageAccounts/qkc32cvb2qmmwsawinvm", 192 | "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/networkInterfaces/myVMNic" 193 | ], 194 | "apiVersion": "2018-10-01", 195 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 196 | "name": "SimpleWinVM", 197 | "type": "Microsoft.Compute/virtualMachines", 198 | "location": "westeurope", 199 | "properties": { 200 | "hardwareProfile": { 201 | "vmSize": "Standard_A2" 202 | }, 203 | "osProfile": { 204 | "computerName": "SimpleWinVM", 205 | "adminUsername": "Henk", 206 | "adminPassword": "Welkom123" 207 | }, 208 | "storageProfile": { 209 | "imageReference": { 210 | "publisher": "MicrosoftWindowsServer", 211 | "offer": "WindowsServer", 212 | "sku": "2016-Datacenter", 213 | "version": "latest" 214 | }, 215 | "osDisk": { 216 | "createOption": "FromImage" 217 | }, 218 | "dataDisks": [ 219 | { 220 | "diskSizeGB": 1023, 221 | "lun": 0, 222 | "createOption": "Empty" 223 | } 224 | ] 225 | }, 226 | "networkProfile": { 227 | "networkInterfaces": [ 228 | { 229 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/networkInterfaces/myVMNic" 230 | } 231 | ] 232 | }, 233 | "diagnosticsProfile": { 234 | "bootDiagnostics": { 235 | "enabled": true, 236 | "storageUri": "[reference(resourceId(\u0027Microsoft.Storage/storageAccounts/\u0027, variables(\u0027storageAccountName\u0027))).primaryEndpoints.blob]" 237 | } 238 | } 239 | } 240 | } 241 | ] 242 | } -------------------------------------------------------------------------------- /Tests/MockObjects/ResultComplete.json: -------------------------------------------------------------------------------- 1 | { 2 | "templateHash": "1220693435534564543", 3 | "parameters": { 4 | "adminUsername": { 5 | "type": "String", 6 | "value": "Henk" 7 | }, 8 | "adminPassword": { 9 | "type": "SecureString" 10 | }, 11 | "dnsLabelPrefix": { 12 | "type": "String", 13 | "value": "eandomrlkajelnadada" 14 | }, 15 | "windowsOSVersion": { 16 | "type": "String", 17 | "value": "2016-Datacenter" 18 | }, 19 | "location": { 20 | "type": "String", 21 | "value": "westeurope" 22 | } 23 | }, 24 | "mode": "Complete", 25 | "provisioningState": "Succeeded", 26 | "timestamp": "2019-07-05T20:45:58.2062472Z", 27 | "duration": "PT0S", 28 | "correlationId": "9363190b-4ba0-461b-a5da-dc736bb382b0", 29 | "providers": [ 30 | { 31 | "namespace": "Microsoft.Storage", 32 | "resourceTypes": [ 33 | { 34 | "resourceType": "storageAccounts", 35 | "locations": [ 36 | "westeurope" 37 | ] 38 | } 39 | ] 40 | }, 41 | { 42 | "namespace": "Microsoft.Network", 43 | "resourceTypes": [ 44 | { 45 | "resourceType": "publicIPAddresses", 46 | "locations": [ 47 | "westeurope" 48 | ] 49 | }, 50 | { 51 | "resourceType": "virtualNetworks", 52 | "locations": [ 53 | "westeurope" 54 | ] 55 | }, 56 | { 57 | "resourceType": "networkInterfaces", 58 | "locations": [ 59 | "westeurope" 60 | ] 61 | } 62 | ] 63 | }, 64 | { 65 | "namespace": "Microsoft.Compute", 66 | "resourceTypes": [ 67 | { 68 | "resourceType": "virtualMachines", 69 | "locations": [ 70 | "westeurope" 71 | ] 72 | } 73 | ] 74 | } 75 | ], 76 | "dependencies": [ 77 | { 78 | "dependsOn": [ 79 | { 80 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP", 81 | "resourceType": "Microsoft.Network/publicIPAddresses", 82 | "resourceName": "myPublicIP" 83 | }, 84 | { 85 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/virtualNetworks/MyVNET", 86 | "resourceType": "Microsoft.Network/virtualNetworks", 87 | "resourceName": "MyVNET" 88 | } 89 | ], 90 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 91 | "resourceType": "Microsoft.Network/networkInterfaces", 92 | "resourceName": "myVMNic" 93 | }, 94 | { 95 | "dependsOn": [ 96 | { 97 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Storage/storageAccounts/qkc32cvb2qmmwsawinvm", 98 | "resourceType": "Microsoft.Storage/storageAccounts", 99 | "resourceName": "qkc32cvb2qmmwsawinvm" 100 | }, 101 | { 102 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 103 | "resourceType": "Microsoft.Network/networkInterfaces", 104 | "resourceName": "myVMNic" 105 | } 106 | ], 107 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 108 | "resourceType": "Microsoft.Compute/virtualMachines", 109 | "resourceName": "SimpleWinVM" 110 | } 111 | ], 112 | "validatedResources": [ 113 | { 114 | "apiVersion": "2018-11-01", 115 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Storage/storageAccounts/qkc32cvb2qmmwsawinvm", 116 | "name": "qkc32cvb2qmmwsawinvm", 117 | "type": "Microsoft.Storage/storageAccounts", 118 | "sku": { 119 | "name": "Standard_LRS" 120 | }, 121 | "kind": "Storage", 122 | "location": "westeurope", 123 | "properties": { 124 | 125 | } 126 | }, 127 | { 128 | "apiVersion": "2018-11-01", 129 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP", 130 | "name": "myPublicIP", 131 | "type": "Microsoft.Network/publicIPAddresses", 132 | "location": "westeurope", 133 | "properties": { 134 | "publicIPAllocationMethod": "Dynamic", 135 | "dnsSettings": { 136 | "domainNameLabel": "eandomrlkajelnadada" 137 | } 138 | } 139 | }, 140 | { 141 | "apiVersion": "2018-11-01", 142 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/virtualNetworks/MyVNET", 143 | "name": "MyVNET", 144 | "type": "Microsoft.Network/virtualNetworks", 145 | "location": "westeurope", 146 | "properties": { 147 | "addressSpace": { 148 | "addressPrefixes": [ 149 | "10.0.0.0/16" 150 | ] 151 | }, 152 | "subnets": [ 153 | { 154 | "name": "Subnet", 155 | "properties": { 156 | "addressPrefix": "10.0.0.0/24" 157 | } 158 | } 159 | ] 160 | } 161 | }, 162 | { 163 | "dependsOn": [ 164 | "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP", 165 | "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/virtualNetworks/MyVNET" 166 | ], 167 | "apiVersion": "2018-11-01", 168 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 169 | "name": "myVMNic", 170 | "type": "Microsoft.Network/networkInterfaces", 171 | "location": "westeurope", 172 | "properties": { 173 | "ipConfigurations": [ 174 | { 175 | "name": "ipconfig1", 176 | "properties": { 177 | "privateIPAllocationMethod": "Dynamic", 178 | "publicIPAddress": { 179 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP" 180 | }, 181 | "subnet": { 182 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/virtualNetworks/MyVNET/subnets/Subnet" 183 | } 184 | } 185 | } 186 | ] 187 | } 188 | }, 189 | { 190 | "dependsOn": [ 191 | "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Storage/storageAccounts/qkc32cvb2qmmwsawinvm", 192 | "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/networkInterfaces/myVMNic" 193 | ], 194 | "apiVersion": "2018-10-01", 195 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 196 | "name": "SimpleWinVM", 197 | "type": "Microsoft.Compute/virtualMachines", 198 | "location": "westeurope", 199 | "properties": { 200 | "hardwareProfile": { 201 | "vmSize": "Standard_A2" 202 | }, 203 | "osProfile": { 204 | "computerName": "SimpleWinVM", 205 | "adminUsername": "Henk", 206 | "adminPassword": "Welkom123" 207 | }, 208 | "storageProfile": { 209 | "imageReference": { 210 | "publisher": "MicrosoftWindowsServer", 211 | "offer": "WindowsServer", 212 | "sku": "2016-Datacenter", 213 | "version": "latest" 214 | }, 215 | "osDisk": { 216 | "createOption": "FromImage" 217 | }, 218 | "dataDisks": [ 219 | { 220 | "diskSizeGB": 1023, 221 | "lun": 0, 222 | "createOption": "Empty" 223 | } 224 | ] 225 | }, 226 | "networkProfile": { 227 | "networkInterfaces": [ 228 | { 229 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/ARMtesting/providers/Microsoft.Network/networkInterfaces/myVMNic" 230 | } 231 | ] 232 | }, 233 | "diagnosticsProfile": { 234 | "bootDiagnostics": { 235 | "enabled": true, 236 | "storageUri": "[reference(resourceId(\u0027Microsoft.Storage/storageAccounts/\u0027, variables(\u0027storageAccountName\u0027))).primaryEndpoints.blob]" 237 | } 238 | } 239 | } 240 | } 241 | ] 242 | } -------------------------------------------------------------------------------- /Tests/MockObjects/ResultCompleteaz.json: -------------------------------------------------------------------------------- 1 | { 2 | "templateHash": "1220693435534564543", 3 | "parameters": { 4 | "adminUsername": { 5 | "type": "String", 6 | "value": "Test" 7 | }, 8 | "adminPassword": { 9 | "type": "SecureString" 10 | }, 11 | "dnsLabelPrefix": { 12 | "type": "String", 13 | "value": "eandomrlkajelnadada" 14 | }, 15 | "windowsOSVersion": { 16 | "type": "String", 17 | "value": "2016-Datacenter" 18 | }, 19 | "location": { 20 | "type": "String", 21 | "value": "westeurope" 22 | } 23 | }, 24 | "mode": "Complete", 25 | "provisioningState": "Succeeded", 26 | "timestamp": "2019-07-05T20:45:58.2062472Z", 27 | "duration": "PT0S", 28 | "correlationId": "93690b-4ba0-461b-a5da-dc7b0", 29 | "providers": [ 30 | { 31 | "namespace": "Microsoft.Storage", 32 | "resourceTypes": "" 33 | }, 34 | { 35 | "namespace": "Microsoft.Network", 36 | "resourceTypes": " " 37 | }, 38 | { 39 | "namespace": "Microsoft.Compute", 40 | "resourceTypes": "" 41 | } 42 | ], 43 | "dependencies": [ 44 | { 45 | "dependsOn": " ", 46 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 47 | "resourceType": "Microsoft.Network/networkInterfaces", 48 | "resourceName": "myVMNic" 49 | }, 50 | { 51 | "dependsOn": " ", 52 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 53 | "resourceType": "Microsoft.Compute/virtualMachines", 54 | "resourceName": "SimpleWinVM" 55 | } 56 | ], 57 | "validatedResources": [ 58 | { 59 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Storage/storageAccounts/mstaqkc32c2qmmw" 60 | }, 61 | { 62 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP" 63 | }, 64 | { 65 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/virtualNetworks/MyVNET" 66 | }, 67 | { 68 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic" 69 | }, 70 | { 71 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM" 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /Tests/MockObjects/Resultaz.json: -------------------------------------------------------------------------------- 1 | { 2 | "templateHash": "1220693435534564543", 3 | "parameters": { 4 | "adminUsername": { 5 | "type": "String", 6 | "value": "Test" 7 | }, 8 | "adminPassword": { 9 | "type": "SecureString" 10 | }, 11 | "dnsLabelPrefix": { 12 | "type": "String", 13 | "value": "eandomrlkajelnadada" 14 | }, 15 | "windowsOSVersion": { 16 | "type": "String", 17 | "value": "2016-Datacenter" 18 | }, 19 | "location": { 20 | "type": "String", 21 | "value": "westeurope" 22 | } 23 | }, 24 | "mode": "Incremental", 25 | "provisioningState": "Succeeded", 26 | "timestamp": "2019-07-05T20:45:58.2062472Z", 27 | "duration": "PT0S", 28 | "correlationId": "93690b-4ba0-461b-a5da-dc7b0", 29 | "providers": [ 30 | { 31 | "namespace": "Microsoft.Storage", 32 | "resourceTypes": "" 33 | }, 34 | { 35 | "namespace": "Microsoft.Network", 36 | "resourceTypes": " " 37 | }, 38 | { 39 | "namespace": "Microsoft.Compute", 40 | "resourceTypes": "" 41 | } 42 | ], 43 | "dependencies": [ 44 | { 45 | "dependsOn": " ", 46 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic", 47 | "resourceType": "Microsoft.Network/networkInterfaces", 48 | "resourceName": "myVMNic" 49 | }, 50 | { 51 | "dependsOn": " ", 52 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM", 53 | "resourceType": "Microsoft.Compute/virtualMachines", 54 | "resourceName": "SimpleWinVM" 55 | } 56 | ], 57 | "validatedResources": [ 58 | { 59 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Storage/storageAccounts/qkc32cvb2qmmwsawinvm" 60 | }, 61 | { 62 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/publicIPAddresses/myPublicIP" 63 | }, 64 | { 65 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/virtualNetworks/MyVNET" 66 | }, 67 | { 68 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Network/networkInterfaces/myVMNic" 69 | }, 70 | { 71 | "id": "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Compute/virtualMachines/SimpleWinVM" 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /Tests/MockObjects/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "storageAccountPrefix": { 6 | "type": "string", 7 | "maxLength": 11, 8 | "defaultValue": "armsta", 9 | "metadata": { 10 | "description": "Prefix the storageaccount" 11 | } 12 | }, 13 | "storageAccountType": { 14 | "type": "string", 15 | "defaultValue": "Standard_LRS", 16 | "allowedValues": [ 17 | "Standard_LRS", 18 | "Premium_LRS", 19 | "Standard_RAGRS" 20 | ], 21 | "metadata": { 22 | "description": "The type for the storage account" 23 | } 24 | } 25 | }, 26 | "variables": {}, 27 | "resources": [], 28 | "outputs": {} 29 | 30 | } -------------------------------------------------------------------------------- /Tests/PSScriptAnalyzer.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Run PSScriptAnalyzertests against all scripts 4 | .DESCRIPTION 5 | This scripts runs check with PSScriptanalyzer. 6 | .PARAMETER ScriptPath 7 | The Path where the scripts are. This could be the whole repository or a specific Scriptfolder. 8 | .PARAMETER Exclusions 9 | Define values that don't need to be checked, because they are and accepted risk. For example: PSAvoidUsingUserNameAndPassWordParams if using a lab environment 10 | .PARAMETER Manual 11 | If used, more tests will be ran. These tests check best practices, but aren't needed to run the script through the build. 12 | .EXAMPLE 13 | .\PSScriptAnalyzer.ps1 -ScriptPath C:\Scripts\ExampleRepo -Exclusions PSAvoidUsingUserNameAndPassWordParams -Manual 14 | Script will run manually and provide all testresults. Rule PSAvoidUsingUserNameAndPassWordParams will be ignored 15 | .NOTES 16 | This script is written to use in a build pipeline, but can be ran manually without issues. 17 | Written by Barbara Forbes 18 | #> 19 | 20 | Param( 21 | [Parameter(Mandatory = $true)] 22 | [String]$ScriptPath, 23 | [Parameter()] 24 | [String[]]$Exclusions, 25 | # Parameter help description 26 | [Parameter()] 27 | [Switch]$Manual 28 | ) 29 | 30 | $Scripts = Get-ChildItem $ScriptPath -Include *.ps1, *.psm1, *.psd1 -Recurse 31 | $ErrorFound = $false 32 | foreach ($Script in $Scripts) { 33 | Write-Output "Checking $($Script.FullName)" 34 | $PSScriptAnalyzer = Invoke-ScriptAnalyzer -Path $Script.fullname 35 | if ($Manual) { 36 | $PSScriptAnalyzer 37 | continue 38 | } 39 | 40 | Foreach ($Result in $PSScriptAnalyzer) { 41 | if (($Result.Severity -eq "Error") -and ($Exclusions -notcontains $Result.RuleName) ) { 42 | Write-Output "Rule triggered:" 43 | Write-Output $Result 44 | $ErrorFound = $true 45 | } 46 | } 47 | } 48 | 49 | if ($ErrorFound -eq $true) { 50 | Exit 1 51 | } 52 | else { 53 | Write-Output "No errors found" 54 | } 55 | -------------------------------------------------------------------------------- /Tests/Test-ARMAzureModule.tests.ps1: -------------------------------------------------------------------------------- 1 | $projectRoot = Resolve-Path "$PSScriptRoot\.." 2 | $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") 3 | $moduleName = Split-Path $moduleRoot -Leaf 4 | 5 | Import-Module (Join-Path $moduleRoot "$moduleName.psd1") -force 6 | 7 | 8 | Describe 'Test-ARMAzureModule' -Tag @("Mock") { 9 | InModuleScope ARMHelper { 10 | Context 'When module is not loaded' { 11 | It "If none are available, script should throw" { 12 | Mock Get-InstalledModule -MockWith { $null } 13 | Mock Get-Module -MockWith { $null } 14 | { Test-ARMAzureModule } | Should -Throw "neither AZ of AzureRM could be loaded" 15 | 16 | } 17 | function Get-AzContext ([String]$Name, [Object]$Value, [Switch]$Clobber) { } 18 | function Get-AzureRMContext([String]$Name, [Object]$Value, [Switch]$Clobber) { } 19 | Mock Get-Module { $null } 20 | Mock Import-Module { $null } 21 | Mock Get-InstalledModule -MockWith { 22 | [pscustomobject]@{ 23 | "Version" = "6.13.1" 24 | "Name" = "AzureRM" 25 | "Repository" = "PSGallery" 26 | "Description" = "Azure Resource Manager Module" 27 | } 28 | } -ParameterFilter { $Name -eq "AzureRM" } 29 | Mock Get-InstalledModule -MockWith { 30 | $null 31 | } -ParameterFilter { $Name -eq "Az" } 32 | Mock Get-AzureRmContext -MockWith { "no error" } 33 | Mock Get-AzContext -MockWith { "no error" } 34 | It "If Only AzureRM is available, output should be AzureRM" { 35 | $Result = Test-ARMAzureModule 36 | $Result | Should -be "AzureRM" 37 | } 38 | It "If Only Az is available, output should be Az" { 39 | Mock Get-InstalledModule -MockWith { 40 | $null 41 | } -ParameterFilter { $Name -eq "AzureRM" } 42 | Mock Get-InstalledModule -MockWith { 43 | [pscustomobject]@{ 44 | "Version" = "2.4.0" 45 | "Name" = "Az" 46 | "Repository" = "PSGallery" 47 | "Description" = "Azure Resource Manager Module" 48 | } 49 | } -ParameterFilter { $Name -eq "Az" } 50 | Mock Import-Module -MockWith { "no error" } 51 | Mock Get-AzContext -MockWith { "no error" } 52 | 53 | $Result = Test-ARMAzureModule 54 | $Result | Should -be "Az" 55 | } 56 | It "If Both are available, output should be Az" { 57 | Mock Get-InstalledModule -MockWith { 58 | [pscustomobject]@{ 59 | "Version" = "6.13.1" 60 | "Name" = "AzureRM" 61 | "Repository" = "PSGallery" 62 | "Description" = "Azure Resource Manager Module" 63 | } 64 | } -ParameterFilter { $Name -eq "AzureRM" } 65 | Mock Get-InstalledModule -MockWith { 66 | [pscustomobject]@{ 67 | "Version" = "2.4.0" 68 | "Name" = "Az" 69 | "Repository" = "PSGallery" 70 | "Description" = "Azure Resource Manager Module" 71 | } 72 | } -ParameterFilter { $Name -eq "Az" } 73 | $Result = Test-ARMAzureModule 74 | $Result | Should -be "Az" 75 | } 76 | it "Fails when no connectoin is found"{ 77 | Mock Get-AzContext { Throw "error"} 78 | { Test-ARMAzureModule } | Should throw "no connection" 79 | } 80 | } 81 | Context 'When module is loaded' { 82 | function Get-AzContext ([String]$Name, [Object]$Value, [Switch]$Clobber) { } 83 | function Get-AzureRMContext([String]$Name, [Object]$Value, [Switch]$Clobber) { } 84 | 85 | Mock Get-AzureRmContext -MockWith { "no error" } 86 | It "If AzureRM is already loaded, output should be AzureRM" { 87 | Mock Get-InstalledModule -MockWith { 88 | $null 89 | } 90 | Mock Get-Module -MockWith { 91 | $null 92 | } -ParameterFilter { $Name -eq "Az.*" } 93 | Mock Get-Module -MockWith { 94 | [array]@( 95 | "module1", 96 | "Module2" 97 | ) 98 | } -ParameterFilter { $Name -eq "AzureRM.*" } 99 | $Result = Test-ARMAzureModule 100 | $Result | Should -be "AzureRM" 101 | } 102 | It "If Az is already loaded, output should be Az" { 103 | Mock Get-InstalledModule -MockWith { 104 | $null 105 | } 106 | Mock Get-Module -MockWith { 107 | $null 108 | } -ParameterFilter { $Name -eq "AzureRM.*" } 109 | Mock Get-Module -MockWith { 110 | [array]@( 111 | "module1", 112 | "Module2" 113 | ) 114 | } -ParameterFilter { $Name -eq "Az.*" } 115 | 116 | Mock Get-AzContext -MockWith { "no error" } 117 | 118 | $Result = Test-ARMAzureModule 119 | $Result | Should -be "Az" 120 | } 121 | 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /Tests/Test-ARMDeploymentResource.tests.ps1: -------------------------------------------------------------------------------- 1 | $projectRoot = Resolve-Path "$PSScriptRoot\.." 2 | $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") 3 | $moduleName = Split-Path $moduleRoot -Leaf 4 | 5 | Import-Module (Join-Path $moduleRoot "$moduleName.psd1") -force 6 | 7 | 8 | Describe 'Check Test-ARMDeploymentResource without Azure' -Tag @("Mock") { 9 | InModuleScope ARMHelper { 10 | $Parameters = @{ 11 | resourcegroupname = "Arm" 12 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 13 | templateparameterfile = ".\azuredeploy.parameters.json" 14 | } 15 | Context 'AZ Basic functionality' { 16 | Mock Test-ARMAzureModule { "Az" } 17 | $Mockobject = (Get-Content "$PSScriptRoot\MockObjects\Resultaz.json") | ConvertFrom-Json 18 | Mock Get-ARMResource { 19 | [object]$Mockobject 20 | } 21 | It "Works with a parameterFile" { 22 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 23 | } 24 | It "works with a parameter object" { 25 | $Parameterobject = @{ 26 | storageAccountPrefix = "armsta" 27 | storageAccountType = "LRS" 28 | } 29 | $Parameters = @{ 30 | resourcegroupname = "Arm" 31 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 32 | templateparameterobject = $Parameterobject 33 | } 34 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 35 | 36 | } 37 | It "works with added Parameters" { 38 | $Parameters = @{ 39 | resourcegroupname = "Arm" 40 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 41 | storageAccountPrefix = "armsta" 42 | storageAccountType = "LRS" 43 | } 44 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 45 | } 46 | 47 | It "When a deployment is correct, script doesn't throw" { 48 | { Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue } | Should -Not -Throw 49 | } 50 | it "Gives a warning for using Az-module"{ 51 | $Parameters = @{ 52 | resourcegroupname = "Arm" 53 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 54 | storageAccountPrefix = "armsta" 55 | storageAccountType = "LRS" 56 | } 57 | $Result = (Test-ARMDeploymentResource @Parameters 3>&1) 58 | $Result.Message[0] | Should -BeLike "The AZ-module is used. This limits the results of this cmdlet*" 59 | 60 | } 61 | It "Shows standard properties for resources that would be in the deployment" { 62 | $Result = Test-ARMDeploymentResource @Parameters -WarningAction SilentlyContinue 63 | $Result[0].Resource | Should -be "StorageAccounts" 64 | $Result[0].Name | Should -be "qkc32cvb2qmmwsawinvm" 65 | $Result[0].Type | Should -be "Microsoft.Storage/storageAccounts" 66 | $Result[0].ID | Should -be "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Storage/storageAccounts/qkc32cvb2qmmwsawinvm" 67 | } 68 | it "Should throw when no result is found" { 69 | Mock Get-ARMResource { $null } 70 | { Test-ARMDeploymentResource @Parameters } | Should throw "Something is wrong with the output, no resources found. Please check your deployment with Get-ARMdeploymentErrorMessage" 71 | } 72 | It "All Mocks are called" { 73 | Assert-MockCalled -CommandName Get-ARMResource 74 | } 75 | } 76 | Context 'AzureRM Basic functionality' { 77 | Mock Test-ARMAzureModule { "AzureRM" } 78 | $Mockobject = (Get-Content "$PSScriptRoot\MockObjects\Result.json") | ConvertFrom-Json 79 | Mock Get-ARMResource { 80 | [object]$Mockobject 81 | } 82 | 83 | It "Works with a parameterFile" { 84 | { Test-ARMDeploymentResource @Parameters } | Should -Not -Throw 85 | } 86 | It "works with a parameter object" { 87 | $Parameterobject = @{ 88 | storageAccountPrefix = "armsta" 89 | storageAccountType = "LRS" 90 | } 91 | $Parameters = @{ 92 | resourcegroupname = "Arm" 93 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 94 | templateparameterobject = $Parameterobject 95 | } 96 | { Test-ARMDeploymentResource @Parameters } | Should -Not -Throw 97 | 98 | } 99 | It "works with added Parameters" { 100 | $Parameters = @{ 101 | resourcegroupname = "Arm" 102 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 103 | storageAccountPrefix = "armsta" 104 | storageAccountType = "LRS" 105 | } 106 | { Test-ARMDeploymentResource @Parameters } | Should -Not -Throw 107 | } 108 | 109 | It "When a deployment is correct, script doesn't throw" { 110 | { Test-ARMDeploymentResource @Parameters } | Should -Not -Throw 111 | } 112 | It "Shows standard properties for resources that would be in the deployment" { 113 | $Result = Test-ARMDeploymentResource @Parameters 114 | $Result[0].Resource | Should -be "StorageAccounts" 115 | $Result[0].Name | Should -be "qkc32cvb2qmmwsawinvm" 116 | $Result[0].Type | Should -be "Microsoft.Storage/storageAccounts" 117 | $Result[0].Location | Should -be "westeurope" 118 | $Result[0].mode | Should -be "Incremental" 119 | $Result[0].ID | Should -be "/subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Storage/storageAccounts/qkc32cvb2qmmwsawinvm" 120 | $Result[0].kind | Should -be "Storage" 121 | $Result[0].sku.name | Should -BeNullOrEmpty 122 | } 123 | It "Shows all properties when using Select-Object *" { 124 | $Result = Test-ARMDeploymentResource @Parameters | Select-Object * 125 | $result[0].'.sku.name' | Should -be "Standard_LRS" 126 | $Result[0].apiVersion | Should -be "2018-11-01" 127 | } 128 | it "When a parameter is a securestring, it is shown as a securestring in the output" { 129 | $Result = Test-ARMDeploymentResource @Parameters 130 | $Result[-1].adminPassword | Should -Be "System.Security.SecureString" 131 | } 132 | it "Should throw when no result is found" { 133 | Mock Get-ARMResource { $null } 134 | { Test-ARMDeploymentResource @Parameters } | Should throw "Something is wrong with the output, no resources found. Please check your deployment with Get-ARMdeploymentErrorMessage" 135 | } 136 | It "All Mocks are called" { 137 | Assert-MockCalled -CommandName Get-ARMResource 138 | } 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /Tests/Test-ARMExistingResource.tests.ps1: -------------------------------------------------------------------------------- 1 | $projectRoot = Resolve-Path "$PSScriptRoot\.." 2 | $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") 3 | $moduleName = Split-Path $moduleRoot -Leaf 4 | 5 | Import-Module (Join-Path $moduleRoot "$moduleName.psd1") -force 6 | 7 | 8 | Describe 'Check Test-ARMExistingResource without Azure' -Tag @("Mock") { 9 | 10 | InModuleScope ARMHelper { 11 | Context 'AZ Incremental' { 12 | function Get-AzResource([String]$Name, [Object]$Value, [Switch]$Clobber) { } 13 | $Parameters = @{ 14 | resourcegroupname = "Arm" 15 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 16 | templateparameterfile = ".\azuredeploy.parameters.json" 17 | Mode = "Incremental" 18 | } 19 | Mock Test-ARMAzureModule { "Az" } 20 | $Mockobject = (Get-Content "$PSScriptRoot\MockObjects\Resultaz.json") | ConvertFrom-Json 21 | Mock Get-ARMResource { 22 | [object]$Mockobject 23 | } 24 | It "When all resources are new, output shows they will be created" { 25 | Mock Get-AzResource { $null } 26 | $Result = Test-ARMExistingResource @Parameters 27 | $Result[0] | Should -Be "Mode for deployment is Incremental `n" 28 | $Result[1] | Should -Be "The following resources do not exist and will be created:" 29 | $Result[2].Type | Should -Be "Microsoft.Storage/storageAccounts" 30 | $Result[2].ResourceGroupName | Should -Be "Arm" 31 | } 32 | It "When resources already exist and mode is incremental, they will be shown as so" { 33 | $MockAZResource = (Get-Content "$PSScriptRoot\MockObjects\ExistingResources.json") | ConvertFrom-Json 34 | Mock Get-AzResource { 35 | [object]$MockAZResource 36 | } 37 | $Result = Test-ARMExistingResource @Parameters 38 | $Result[0] | Should -Be "Mode for deployment is Incremental `n" 39 | $Result[1] | Should -Be "The following resources exist. Mode is set to incremental. New properties might be added:" 40 | $Result[2].Type | Should -Be "Microsoft.Storage/storageAccounts" 41 | $Result[2].ResourceGroupName | Should -Be "Arm" 42 | } 43 | it "When a Microsoft.Deploy is used, a warning is given."{ 44 | $Mockobject = (Get-Content "$PSScriptRoot\MockObjects\NestedResult.json") | ConvertFrom-Json 45 | Mock Get-ARMResource { 46 | [object]$Mockobject 47 | } 48 | $Result = (Test-ARMExistingResource @Parameters 3>&1) 49 | $Result.Message[0] | Should -Be "This command does not work for the resourcetype Microsoft.Resources/deployments. Please check linkedTemplate manually." 50 | 51 | } 52 | } 53 | Context 'Az Complete ' { 54 | function Get-AzResource([String]$Name, [Object]$Value, [Switch]$Clobber) { } 55 | $Parameters = @{ 56 | resourcegroupname = "Arm" 57 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 58 | templateparameterfile = ".\azuredeploy.parameters.json" 59 | Mode = "Complete" 60 | } 61 | $Mockobject = (Get-Content "$PSScriptRoot\MockObjects\ResultCompleteaz.json") | ConvertFrom-Json 62 | Mock Get-ARMResource { 63 | [object]$Mockobject 64 | } 65 | Mock Test-ARMAzureModule { "Az" } 66 | It "When resources would be overwritten, they are shown as overwritten" { 67 | $MockAZResource = (Get-Content "$PSScriptRoot\MockObjects\ExistingResources.json") | ConvertFrom-Json 68 | Mock Get-AzResource { 69 | [object]$MockAZResource 70 | } 71 | $Result = Test-ARMExistingResource @Parameters 72 | $Result[0] | Should -Be "Mode for deployment is Complete `n" 73 | $Result[1] | Should -Be "THE FOLLOWING RESOURCES WILL BE OVERWRITTEN! `n Resources exist and mode is complete:" 74 | $Result[2].Type | Should -Be "Microsoft.Storage/storageAccounts" 75 | $Result[2].ResourceGroupName | Should -Be "Arm" 76 | } 77 | it "When resources would be deleted, they are shown as deleted" { 78 | $MockAZResource = (Get-Content "$PSScriptRoot\MockObjects\ExistingResourcesDeleted.json") | ConvertFrom-Json 79 | Mock Get-AzResource { 80 | [object]$MockAZResource 81 | } 82 | $Result = Test-ARMExistingResource @Parameters 83 | $Result[0] | Should -Be "Mode for deployment is Complete `n" 84 | $Result[8] | Should -Be "THE FOLLOWING RESOURCES WILL BE DELETED! `n Resources exist in the resourcegroup but not in the template, mode is complete:" 85 | $Result[9].Type | Should -Be "Microsoft.Storage/storageAccounts" 86 | $Result[9].ResourceGroupName | Should -Be "Arm" 87 | } 88 | it "When ThrowWhenRemoving is used, it will throw if resources are deleted" { 89 | $MockAZResource = (Get-Content "$PSScriptRoot\MockObjects\ExistingResourcesDeleted.json") | ConvertFrom-Json 90 | Mock Get-AzResource { 91 | [object]$MockAZResource 92 | } 93 | { Test-ARMExistingResource @Parameters -ThrowWhenRemoving } | Should throw 94 | } 95 | it "When ThrowWhenRemoving is used, but nothing would be overwritten, it will not throw" { 96 | $MockAZResource = (Get-Content "$PSScriptRoot\MockObjects\ExistingResources.json") | ConvertFrom-Json 97 | Mock Get-AzResource { 98 | [object]$MockAZResource 99 | } 100 | { Test-ARMExistingResource @Parameters -ThrowWhenRemoving } | Should throw 101 | } 102 | it "Should throw when no result is found" { 103 | Mock Get-ARMResource { $null } 104 | { Test-ARMExistingResource @Parameters } | Should throw "Something is wrong with the output, no resources found. Please check your deployment with Get-ARMdeploymentErrorMessage" 105 | } 106 | It "Mocks are called" { 107 | Assert-MockCalled -CommandName Get-ARMResource 108 | Assert-MockCalled -CommandName Get-AZResource 109 | } 110 | } 111 | 112 | 113 | Context 'AzureRM Incremental' { 114 | function Get-AzureRMResource([String]$Name, [Object]$Value, [Switch]$Clobber) { } 115 | $Parameters = @{ 116 | resourcegroupname = "Arm" 117 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 118 | templateparameterfile = ".\azuredeploy.parameters.json" 119 | Mode = "Incremental" 120 | } 121 | Mock Test-ARMAzureModule { "AzureRM" } 122 | $Mockobject = (Get-Content "$PSScriptRoot\MockObjects\Result.json") | ConvertFrom-Json 123 | Mock Get-ARMResource { 124 | [object]$Mockobject 125 | } 126 | It "When all resources are new, output shows they will be created" { 127 | 128 | Mock Get-AzureRMResource { $null } 129 | $Result = Test-ARMExistingResource @Parameters 130 | $Result[0] | Should -Be "Mode for deployment is Incremental `n" 131 | $Result[1] | Should -Be "The following resources do not exist and will be created:" 132 | $Result[2].Type | Should -Be "Microsoft.Storage/storageAccounts" 133 | $Result[2].ResourceGroupName | Should -Be "Arm" 134 | } 135 | It "When resources already exist and mode is incremental, they will be shown as so" { 136 | $MockAZResource = (Get-Content "$PSScriptRoot\MockObjects\ExistingResources.json") | ConvertFrom-Json 137 | Mock Get-AzureRMResource { 138 | [object]$MockAZResource 139 | } 140 | $Result = Test-ARMExistingResource @Parameters 141 | $Result[0] | Should -Be "Mode for deployment is Incremental `n" 142 | $Result[1] | Should -Be "The following resources exist. Mode is set to incremental. New properties might be added:" 143 | $Result[2].Type | Should -Be "Microsoft.Storage/storageAccounts" 144 | $Result[2].ResourceGroupName | Should -Be "Arm" 145 | 146 | } 147 | } 148 | Context 'Complete ' { 149 | 150 | function Get-AzureRMResource([String]$Name, [Object]$Value, [Switch]$Clobber) { } 151 | $Parameters = @{ 152 | resourcegroupname = "Arm" 153 | templatefile = "$PSScriptRoot\MockObjects\azuredeploy.json" 154 | templateparameterfile = ".\azuredeploy.parameters.json" 155 | Mode = "Complete" 156 | } 157 | $Mockobject = (Get-Content "$PSScriptRoot\MockObjects\ResultComplete.json") | ConvertFrom-Json 158 | Mock Get-ARMResource { 159 | [object]$Mockobject 160 | } 161 | Mock Test-ARMAzureModule { "AzureRM" } 162 | It "When resources would be overwritten, they are shown as overwritten" { 163 | $MockAZResource = (Get-Content "$PSScriptRoot\MockObjects\ExistingResources.json") | ConvertFrom-Json 164 | Mock Get-AzureRMResource { 165 | [object]$MockAZResource 166 | } 167 | $Result = Test-ARMExistingResource @Parameters 168 | $Result[0] | Should -Be "Mode for deployment is Complete `n" 169 | $Result[1] | Should -Be "THE FOLLOWING RESOURCES WILL BE OVERWRITTEN! `n Resources exist and mode is complete:" 170 | $Result[2].Type | Should -Be "Microsoft.Storage/storageAccounts" 171 | $Result[2].ResourceGroupName | Should -Be "Arm" 172 | } 173 | it "When resources would be deleted, they are shown as deleted" { 174 | $MockAZResource = (Get-Content "$PSScriptRoot\MockObjects\ExistingResourcesDeleted.json") | ConvertFrom-Json 175 | Mock Get-AzureRMResource { 176 | [object]$MockAZResource 177 | } 178 | $Result = Test-ARMExistingResource @Parameters 179 | $Result[0] | Should -Be "Mode for deployment is Complete `n" 180 | $Result[8] | Should -Be "THE FOLLOWING RESOURCES WILL BE DELETED! `n Resources exist in the resourcegroup but not in the template, mode is complete:" 181 | $Result[9].Type | Should -Be "Microsoft.Storage/storageAccounts" 182 | $Result[9].ResourceGroupName | Should -Be "Arm" 183 | } 184 | it "When ThrowWhenRemoving is used, it will throw if resources are deleted" { 185 | $MockAZResource = (Get-Content "$PSScriptRoot\MockObjects\ExistingResourcesDeleted.json") | ConvertFrom-Json 186 | Mock Get-AzureRMResource { 187 | [object]$MockAZResource 188 | } 189 | { Test-ARMExistingResource @Parameters -ThrowWhenRemoving } | Should throw 190 | } 191 | it "When ThrowWhenRemoving is used, but nothing would be overwritten, it will not throw" { 192 | $MockAZResource = (Get-Content "$PSScriptRoot\MockObjects\ExistingResources.json") | ConvertFrom-Json 193 | Mock Get-AzureRMResource { 194 | [object]$MockAZResource 195 | } 196 | { Test-ARMExistingResource @Parameters -ThrowWhenRemoving } | Should throw 197 | } 198 | it "Should throw when no result is found" { 199 | Mock Get-ARMResource { $null } 200 | { Test-ARMExistingResource @Parameters } | Should throw "Something is wrong with the output, no resources found. Please check your deployment with Get-ARMdeploymentErrorMessage" 201 | } 202 | It "Mocks are called" { 203 | Assert-MockCalled -CommandName Get-ARMResource 204 | Assert-MockCalled -CommandName Get-AzureRMResource 205 | } 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: Production 2 | trigger: 3 | branches: 4 | include: 5 | - 'master' 6 | 7 | 8 | pr: 9 | - master 10 | 11 | stages: 12 | - stage: Build 13 | jobs: 14 | - job: BuildWindows 15 | pool: 16 | vmImage: windows-2019 17 | steps: 18 | - template: azure-pipelinestemplate.yml 19 | parameters: 20 | platform: 'Windows' 21 | - template: azure-pipelinesazuretemplate.yml 22 | - job: BuildMacOS 23 | pool: 24 | vmImage: macOS-10.14 25 | steps: 26 | - template: azure-pipelinestemplate.yml 27 | parameters: 28 | platform: 'MacOS' 29 | - job: BuildLinux 30 | pool: 31 | vmImage: ubuntu-16.04 32 | steps: 33 | - template: azure-pipelinestemplate.yml 34 | parameters: 35 | platform: 'Linux' 36 | 37 | - stage: Deploy 38 | condition: 39 | and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 40 | jobs: 41 | - job: DeploytoGallery 42 | pool: 43 | vmImage: windows-2019 44 | steps: 45 | - powershell: | 46 | Publish-Module -Path "$(Build.SourcesDirectory)/ARMHelper" -NuGetApiKey "$(GalleryApiKey)" 47 | displayName: 'Deploy to Gallery' 48 | -------------------------------------------------------------------------------- /azure-pipelinesazuretemplate.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | platform: '' 3 | 4 | steps: 5 | - task: AzurePowerShell@3 6 | displayName: 'Pester testing with Azure - v3 ${{ parameters.platform }}' 7 | inputs: 8 | azureSubscription: AzureConnection 9 | ScriptType: InlineScript 10 | Inline: | 11 | $outputFile = "$(Build.SourcesDirectory)\TEST-RESULTS.xml" 12 | Invoke-Pester -Script '$(Build.SourcesDirectory)\Tests\AzureTesting\pipelinetest.ps1' -OutputFile $outputFile -OutputFormat NUnitXml -enableExit 13 | FailOnStandardError: false 14 | azurePowerShellVersion: LatestVersion 15 | continueOnError: true 16 | 17 | - task: AzurePowerShell@4 18 | displayName: 'Pester testing with Azure -v4 ${{ parameters.platform }}' 19 | inputs: 20 | azureSubscription: AzureConnection 21 | ScriptType: InlineScript 22 | Inline: | 23 | $outputFile = "$(Build.SourcesDirectory)\TEST-RESULTS.xml" 24 | Invoke-Pester -Script '$(Build.SourcesDirectory)\Tests\AzureTesting\pipelinetest.ps1' -OutputFile $outputFile -OutputFormat NUnitXml -enableExit 25 | FailOnStandardError: false 26 | azurePowerShellVersion: LatestVersion 27 | continueOnError: true 28 | 29 | 30 | - task: PublishTestResults@2 31 | displayName: 'Publish Online Test Results ${{ parameters.platform }}' 32 | inputs: 33 | testRunTitle: 'online Pester Results Azure ${{ parameters.platform }}' 34 | buildPlatform: 'Azure ${{ parameters.platform }}' 35 | testRunner: 'NUnit' 36 | testResultsFiles: './TEST-RESULTS.xml' 37 | failTaskOnFailedTests: true 38 | -------------------------------------------------------------------------------- /azure-pipelinestemplate.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | platform: '' 3 | 4 | steps: 5 | - powershell: | 6 | Get-ChildItem $(Build.SourcesDirectory)/ARMHelper 7 | Install-Module -Name Pester -Force -Scope CurrentUser -SkipPublisherCheck 8 | Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser 9 | Import-Module "$(Build.SourcesDirectory)/ARMHelper" -Force 10 | displayName: 'Install Pester and import module' 11 | - powershell: | 12 | $outputFile = ".\TEST-RESULTS.xml" 13 | Invoke-Pester -Tag Mock, Module -OutputFile $outputFile -OutputFormat NUnitXml -enableExit 14 | failOnStderr: false 15 | continueOnError: true 16 | displayName: 'Invoke-Pester without Azure' 17 | - task: PublishTestResults@2 18 | displayName: 'Publish Offline Test Results' 19 | inputs: 20 | testRunTitle: "Offline Pester Results ${{ parameters.platform }}" 21 | buildPlatform: ${{ parameters.platform }} 22 | testRunner: 'NUnit' 23 | testResultsFiles: './TEST-RESULTS.xml' 24 | failTaskOnFailedTests: true 25 | 26 | -------------------------------------------------------------------------------- /docs/Get-ARMDeploymentErrorMessage.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: ARMHelper-help.xml 3 | Module Name: ARMHelper 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-ARMDeploymentErrorMessage 9 | 10 | ## SYNOPSIS 11 | Tests an azure deployment for errors, Use the azure Logs if a generic message is given. 12 | 13 | ## SYNTAX 14 | 15 | ### __AllParameterSets (Default) 16 | ``` 17 | Get-ARMDeploymentErrorMessage [-ResourceGroupName] [-TemplateFile] [-Pipeline] 18 | [-ThrowOnError] [] 19 | ``` 20 | 21 | ### TemplateParameterFile 22 | ``` 23 | Get-ARMDeploymentErrorMessage [-ResourceGroupName] [-TemplateFile] 24 | -TemplateParameterFile [-Pipeline] [-ThrowOnError] [] 25 | ``` 26 | 27 | ### TemplateParameterObject 28 | ``` 29 | Get-ARMDeploymentErrorMessage [-ResourceGroupName] [-TemplateFile] 30 | -TemplateParameterObject [-Pipeline] [-ThrowOnError] [] 31 | ``` 32 | 33 | ## DESCRIPTION 34 | This function uses Test-AzureRmResourceGroupDeployment or Test-AZResourcegroupDeployment. 35 | There is a specific errormessage that's very generic. 36 | If this is the output, the correct errormessage is retrieved from the Azurelog. 37 | 38 | ## EXAMPLES 39 | 40 | ### EXAMPLE 1 41 | ``` 42 | Get-ARMDeploymentErrorMessage -ResourceGroupName ArmTest -TemplateFile .\azuredeploy.json -TemplateParameterFile .\azuredeploy.parameters.json 43 | ``` 44 | 45 | -------- 46 | the output is a generic error message. The log is searched for a more clear errormessageGeneral Error. Find info below: 47 | ErrorCode: InvalidDomainNameLabel 48 | Errormessage: The domain name label LABexample is invalid. It must conform to the following regular expression: ^\[a-z\]\[a-z0-9-\]{1,61}\[a-z0-9\]$. 49 | 50 | ### EXAMPLE 2 51 | ``` 52 | Get-ARMDeploymentErrorMessage Armtesting .\VM01\azuredeploy.json -TemplateParameterObject $Parameters 53 | ``` 54 | 55 | -------- 56 | deployment is correct 57 | 58 | ## PARAMETERS 59 | 60 | ### -ResourceGroupName 61 | The resourcegroup where the resources would be deployed to. 62 | This resourcegroup needs to exist. 63 | 64 | ```yaml 65 | Type: String 66 | Parameter Sets: (All) 67 | Aliases: 68 | 69 | Required: True 70 | Position: 2 71 | Default value: None 72 | Accept pipeline input: False 73 | Accept wildcard characters: False 74 | ``` 75 | 76 | ### -TemplateFile 77 | The path to the templatefile 78 | 79 | ```yaml 80 | Type: String 81 | Parameter Sets: (All) 82 | Aliases: 83 | 84 | Required: True 85 | Position: 3 86 | Default value: None 87 | Accept pipeline input: False 88 | Accept wildcard characters: False 89 | ``` 90 | 91 | ### -TemplateParameterFile 92 | The path to the parameterfile, optional 93 | 94 | ```yaml 95 | Type: String 96 | Parameter Sets: TemplateParameterFile 97 | Aliases: 98 | 99 | Required: True 100 | Position: Named 101 | Default value: None 102 | Accept pipeline input: False 103 | Accept wildcard characters: False 104 | ``` 105 | 106 | ### -TemplateParameterObject 107 | A Hasbtable with parameters, optional 108 | 109 | ```yaml 110 | Type: Hashtable 111 | Parameter Sets: TemplateParameterObject 112 | Aliases: 113 | 114 | Required: True 115 | Position: Named 116 | Default value: None 117 | Accept pipeline input: False 118 | Accept wildcard characters: False 119 | ``` 120 | 121 | ### -Pipeline 122 | Use this parameter if this script is used in a CICDpipeline. 123 | It will make the step fail. 124 | This parameter is replaced by ThrowOnError and will be removed in a later release! 125 | 126 | ```yaml 127 | Type: SwitchParameter 128 | Parameter Sets: (All) 129 | Aliases: 130 | 131 | Required: False 132 | Position: Named 133 | Default value: False 134 | Accept pipeline input: False 135 | Accept wildcard characters: False 136 | ``` 137 | 138 | ### -ThrowOnError 139 | This Switch will make the cmdlet throw when the deployment is incorrect. 140 | This can be useful in a pipeline, it will make the task fail. 141 | 142 | ```yaml 143 | Type: SwitchParameter 144 | Parameter Sets: (All) 145 | Aliases: 146 | 147 | Required: False 148 | Position: Named 149 | Default value: False 150 | Accept pipeline input: False 151 | Accept wildcard characters: False 152 | ``` 153 | 154 | ### CommonParameters 155 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 156 | 157 | ## INPUTS 158 | 159 | ## OUTPUTS 160 | 161 | ## NOTES 162 | Dynamic Parameters like in the orginal Test-AzResourcegroupDeployment-cmdlet are supported 163 | Author: Barbara Forbes 164 | Module: ARMHelper 165 | https://4bes.nl 166 | @Ba4bes 167 | 168 | ## RELATED LINKS 169 | -------------------------------------------------------------------------------- /docs/Test-ARMDeploymentResource.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: ARMHelper-help.xml 3 | Module Name: ARMHelper 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Test-ARMDeploymentResource 9 | 10 | ## SYNOPSIS 11 | Gives output that shows all resources that would be deployed by an ARMtemplate 12 | 13 | ## SYNTAX 14 | 15 | ### __AllParameterSets (Default) 16 | ``` 17 | Test-ARMDeploymentResource [-ResourceGroupName] [-TemplateFile] [-Mode ] 18 | [] 19 | ``` 20 | 21 | ### TemplateParameterFile 22 | ``` 23 | Test-ARMDeploymentResource [-ResourceGroupName] [-TemplateFile] 24 | -TemplateParameterFile [-Mode ] [] 25 | ``` 26 | 27 | ### TemplateParameterObject 28 | ``` 29 | Test-ARMDeploymentResource [-ResourceGroupName] [-TemplateFile] 30 | -TemplateParameterObject [-Mode ] [] 31 | ``` 32 | 33 | ## DESCRIPTION 34 | When you enter a ARM template and a parameter file, this function will show what would be deployed 35 | To do this, it used the debug output of Test-AzureRmResourceGroupDeployment or Test-AzResourceGroupDeployment. 36 | A list of all the resources is provided with the most important properties. 37 | Some resources have seperated functions to structure the output. 38 | If no function is available, a generic output will be given. 39 | 40 | ## EXAMPLES 41 | 42 | ### EXAMPLE 1 43 | ``` 44 | Test-ARMDeploymentResource -ResourceGroupName Armtest -TemplateFile .\azuredeploy.json -TemplateParameterFile .\azuredeploy.parameters.json 45 | ``` 46 | 47 | -------- 48 | Resource : storageAccounts 49 | Name : armsta12356 50 | Type : Microsoft.Storage/storageAccounts 51 | Location : westeurope 52 | mode : Incremental 53 | ID : /subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/arm/providers/Microsoft.Storage/storageAccounts/armsta12356 54 | 55 | ### EXAMPLE 2 56 | ``` 57 | Test-ARMDeploymentResource armtesting .\azuredeploy.json -TemplateParameterObject $parameters | select * 58 | ``` 59 | 60 | -------- 61 | Resource : storageAccounts 62 | Name : armsta12356 63 | Type : Microsoft.Storage/storageAccounts 64 | ID : /subscriptions/12345678-abcd-1234-1234-12345678/resourceGroups/armtesting/providers/Microsoft.Storage/storageAccounts/armsta12356 65 | Location : westeurope 66 | Tags: ARMcreated : True 67 | accountType : Standard_LRS 68 | apiVersion : 2015-06-15 69 | Tags: displayName : armsta12356 70 | mode : Incremental 71 | 72 | ## PARAMETERS 73 | 74 | ### -ResourceGroupName 75 | {{ Fill ResourceGroupName Description }} 76 | 77 | ```yaml 78 | Type: String 79 | Parameter Sets: (All) 80 | Aliases: 81 | 82 | Required: True 83 | Position: 2 84 | Default value: None 85 | Accept pipeline input: False 86 | Accept wildcard characters: False 87 | ``` 88 | 89 | ### -TemplateFile 90 | The path to the templatefile 91 | 92 | ```yaml 93 | Type: String 94 | Parameter Sets: (All) 95 | Aliases: 96 | 97 | Required: True 98 | Position: 3 99 | Default value: None 100 | Accept pipeline input: False 101 | Accept wildcard characters: False 102 | ``` 103 | 104 | ### -TemplateParameterFile 105 | The path to the parameterfile, optional 106 | 107 | ```yaml 108 | Type: String 109 | Parameter Sets: TemplateParameterFile 110 | Aliases: 111 | 112 | Required: True 113 | Position: Named 114 | Default value: None 115 | Accept pipeline input: False 116 | Accept wildcard characters: False 117 | ``` 118 | 119 | ### -TemplateParameterObject 120 | A Hasbtable with parameters, optional 121 | 122 | ```yaml 123 | Type: Hashtable 124 | Parameter Sets: TemplateParameterObject 125 | Aliases: 126 | 127 | Required: True 128 | Position: Named 129 | Default value: None 130 | Accept pipeline input: False 131 | Accept wildcard characters: False 132 | ``` 133 | 134 | ### -Mode 135 | {{ Fill Mode Description }} 136 | 137 | ```yaml 138 | Type: String 139 | Parameter Sets: (All) 140 | Aliases: 141 | 142 | Required: False 143 | Position: Named 144 | Default value: Incremental 145 | Accept pipeline input: False 146 | Accept wildcard characters: False 147 | ``` 148 | 149 | ### CommonParameters 150 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 151 | 152 | ## INPUTS 153 | 154 | ## OUTPUTS 155 | 156 | ## NOTES 157 | Dynamic Parameters like in the orginal Test-AzResourcegroupDeployment-cmdlet are supported 158 | Script can be used in a CICD pipeline 159 | Author: Barbara Forbes 160 | Module: ARMHelper 161 | https://4bes.nl 162 | @Ba4bes 163 | Source for more output: #Source https://blog.mexia.com.au/testing-arm-templates-with-pester 164 | 165 | ## RELATED LINKS 166 | -------------------------------------------------------------------------------- /docs/Test-ARMExistingResource.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: ARMHelper-help.xml 3 | Module Name: ARMHelper 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Test-ARMExistingResource 9 | 10 | ## SYNOPSIS 11 | Show if resource that are set to be deployed already exist 12 | 13 | ## SYNTAX 14 | 15 | ### __AllParameterSets (Default) 16 | ``` 17 | Test-ARMExistingResource [-ResourceGroupName] [-TemplateFile] [-Mode ] 18 | [-ThrowWhenRemoving] [] 19 | ``` 20 | 21 | ### TemplateParameterFile 22 | ``` 23 | Test-ARMExistingResource [-ResourceGroupName] [-TemplateFile] -TemplateParameterFile 24 | [-Mode ] [-ThrowWhenRemoving] [] 25 | ``` 26 | 27 | ### TemplateParameterObject 28 | ``` 29 | Test-ARMExistingResource [-ResourceGroupName] [-TemplateFile] 30 | -TemplateParameterObject [-Mode ] [-ThrowWhenRemoving] [] 31 | ``` 32 | 33 | ## DESCRIPTION 34 | This function uses Test-AzureRmResourceGroupDeployment or Test-AzResourceGroupDeployment with debug output to find out what resources are deployed. 35 | After that, it checks if those resources exist in Azure. 36 | It will output the results when using complete mode or incremental mode (depending on the ARM template) 37 | 38 | ## EXAMPLES 39 | 40 | ### EXAMPLE 1 41 | ``` 42 | Test-ARMexistingResource -ResourceGroupName ArmTest -TemplateFile .\azuredeploy.json -TemplateParameterFile .\azuredeploy.parameters.json 43 | ``` 44 | 45 | -------- 46 | The following resources exist. Mode is set to incremental. New properties might be added: 47 | 48 | type name Current ResourcegroupName 49 | ---- ---- ------------------------- 50 | Microsoft.Storage/storageAccounts armsta armtest 51 | 52 | ## PARAMETERS 53 | 54 | ### -ResourceGroupName 55 | The resourcegroup where the resources would be deployed to. 56 | This resourcegroup needs to exist. 57 | 58 | ```yaml 59 | Type: String 60 | Parameter Sets: (All) 61 | Aliases: 62 | 63 | Required: True 64 | Position: 2 65 | Default value: None 66 | Accept pipeline input: False 67 | Accept wildcard characters: False 68 | ``` 69 | 70 | ### -TemplateFile 71 | The path to the deploymentfile 72 | 73 | ```yaml 74 | Type: String 75 | Parameter Sets: (All) 76 | Aliases: 77 | 78 | Required: True 79 | Position: 3 80 | Default value: None 81 | Accept pipeline input: False 82 | Accept wildcard characters: False 83 | ``` 84 | 85 | ### -TemplateParameterFile 86 | The path to the parameterfile 87 | 88 | ```yaml 89 | Type: String 90 | Parameter Sets: TemplateParameterFile 91 | Aliases: 92 | 93 | Required: True 94 | Position: Named 95 | Default value: None 96 | Accept pipeline input: False 97 | Accept wildcard characters: False 98 | ``` 99 | 100 | ### -TemplateParameterObject 101 | {{ Fill TemplateParameterObject Description }} 102 | 103 | ```yaml 104 | Type: Hashtable 105 | Parameter Sets: TemplateParameterObject 106 | Aliases: 107 | 108 | Required: True 109 | Position: Named 110 | Default value: None 111 | Accept pipeline input: False 112 | Accept wildcard characters: False 113 | ``` 114 | 115 | ### -Mode 116 | The mode in which the deployment will run. 117 | Choose between Incremental or Complete. 118 | Defaults to incremental. 119 | 120 | ```yaml 121 | Type: String 122 | Parameter Sets: (All) 123 | Aliases: 124 | 125 | Required: False 126 | Position: Named 127 | Default value: Incremental 128 | Accept pipeline input: False 129 | Accept wildcard characters: False 130 | ``` 131 | 132 | ### -ThrowWhenRemoving 133 | This switch makes the function throw when a resources would be overwritten or deleted. 134 | This can be useful for use in a pipeline. 135 | 136 | ```yaml 137 | Type: SwitchParameter 138 | Parameter Sets: (All) 139 | Aliases: 140 | 141 | Required: False 142 | Position: Named 143 | Default value: False 144 | Accept pipeline input: False 145 | Accept wildcard characters: False 146 | ``` 147 | 148 | ### CommonParameters 149 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 150 | 151 | ## INPUTS 152 | 153 | ## OUTPUTS 154 | 155 | ## NOTES 156 | Dynamic Parameters like in the orginal Test-AzResourcegroupDeployment-cmdlet are supported 157 | Author: Barbara Forbes 158 | Module: ARMHelper 159 | https://4bes.nl 160 | @Ba4bes 161 | 162 | ## RELATED LINKS 163 | --------------------------------------------------------------------------------