Add support for detecting and updating splatted parameters (#68)

* Splatted parameter support (part 1)

* Added unit test support for splatted parameter detection

* Fixed failing unit test from missing property

* Added splatted parameter example scripts

* Removed warnings for splatted parameter detection

* Added matched az upgraded sample scripts

* Fixed off-by-one bug and updated unit tests

* Added support for splatting with ordered hashtable
This commit is contained in:
Keith Babinec 2020-12-14 01:50:15 -08:00 коммит произвёл GitHub
Родитель f0f367f33e
Коммит 69d88f7dca
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
21 изменённых файлов: 631 добавлений и 177 удалений

Просмотреть файл

@ -0,0 +1,25 @@
# Original source code: https://github.com/Azure/azure-docs-powershell-samples/blob/a513b6fceae51aaea1daaa8edd4d6fc66590d172/virtual-machine/create-vm-detailed/create-windows-vm-quick.ps1
# Variables for common values
$resourceGroup = "myResourceGroup"
$location = "westeurope"
$vmName = "myVM"
# Create user object
$cred = Get-Credential -Message "Enter a username and password for the virtual machine."
# Create a resource group
New-AzResourceGroup -Name $resourceGroup -Location $location
# Create a virtual machine
# use splatted params
$virtualMachineParams = @{
Location = $location
Image = "Win2016Datacenter"
VirtualNetworkName = "myVnet"
SubnetName = "mySubnet"
SecurityGroupName = "myNetworkSecurityGroup"
PublicIpAddressName = "myPublicIp"
Credential = $cred
OpenPorts = 3389
}
New-AzVM @virtualMachineParams -ResourceGroupName $resourceGroup -Name $vmName

Просмотреть файл

@ -0,0 +1,25 @@
# Original source code: https://github.com/Azure/azure-docs-powershell-samples/blob/a513b6fceae51aaea1daaa8edd4d6fc66590d172/virtual-machine/create-vm-detailed/create-windows-vm-quick.ps1
# Variables for common values
$resourceGroup = "myResourceGroup"
$location = "westeurope"
$vmName = "myVM"
# Create user object
$cred = Get-Credential -Message "Enter a username and password for the virtual machine."
# Create a resource group
New-AzResourceGroup -Name $resourceGroup -Location $location
# Create a virtual machine
# use splatted params (keys are wrapped with quotes)
$virtualMachineParams = @{
"Location" = $location
"Image" = "Win2016Datacenter"
"VirtualNetworkName" = "myVnet"
"SubnetName" = "mySubnet"
"SecurityGroupName" = "myNetworkSecurityGroup"
"PublicIpAddressName" = "myPublicIp"
"Credential" = $cred
"OpenPorts" = 3389
}
New-AzVM @virtualMachineParams -ResourceGroupName $resourceGroup -Name $vmName

Просмотреть файл

@ -0,0 +1,25 @@
# Original source code: https://github.com/Azure/azure-docs-powershell-samples/blob/a513b6fceae51aaea1daaa8edd4d6fc66590d172/virtual-machine/create-vm-detailed/create-windows-vm-quick.ps1
# Variables for common values
$resourceGroup = "myResourceGroup"
$location = "westeurope"
$vmName = "myVM"
# Create user object
$cred = Get-Credential -Message "Enter a username and password for the virtual machine."
# Create a resource group
New-AzResourceGroup -Name $resourceGroup -Location $location
# Create a virtual machine
# use splatted params
$virtualMachineParams = [ordered]@{
Location = $location
Image = "Win2016Datacenter"
VirtualNetworkName = "myVnet"
SubnetName = "mySubnet"
SecurityGroupName = "myNetworkSecurityGroup"
PublicIpAddressName = "myPublicIp"
Credential = $cred
OpenPorts = 3389
}
New-AzVM @virtualMachineParams -ResourceGroupName $resourceGroup -Name $vmName

Просмотреть файл

@ -0,0 +1,25 @@
# Original source code: https://github.com/Azure/azure-docs-powershell-samples/blob/a513b6fceae51aaea1daaa8edd4d6fc66590d172/virtual-machine/create-vm-detailed/create-windows-vm-quick.ps1
# Variables for common values
$resourceGroup = "myResourceGroup"
$location = "westeurope"
$vmName = "myVM"
# Create user object
$cred = Get-Credential -Message "Enter a username and password for the virtual machine."
# Create a resource group
New-AzureRmResourceGroup -Name $resourceGroup -Location $location
# Create a virtual machine
# use splatted params
$virtualMachineParams = @{
Location = $location
ImageName = "Win2016Datacenter"
VirtualNetworkName = "myVnet"
SubnetName = "mySubnet"
SecurityGroupName = "myNetworkSecurityGroup"
PublicIpAddressName = "myPublicIp"
Credential = $cred
OpenPorts = 3389
}
New-AzureRmVM @virtualMachineParams -ResourceGroupName $resourceGroup -Name $vmName

Просмотреть файл

@ -0,0 +1,25 @@
# Original source code: https://github.com/Azure/azure-docs-powershell-samples/blob/a513b6fceae51aaea1daaa8edd4d6fc66590d172/virtual-machine/create-vm-detailed/create-windows-vm-quick.ps1
# Variables for common values
$resourceGroup = "myResourceGroup"
$location = "westeurope"
$vmName = "myVM"
# Create user object
$cred = Get-Credential -Message "Enter a username and password for the virtual machine."
# Create a resource group
New-AzureRmResourceGroup -Name $resourceGroup -Location $location
# Create a virtual machine
# use splatted params (keys are wrapped with quotes)
$virtualMachineParams = @{
"Location" = $location
"ImageName" = "Win2016Datacenter"
"VirtualNetworkName" = "myVnet"
"SubnetName" = "mySubnet"
"SecurityGroupName" = "myNetworkSecurityGroup"
"PublicIpAddressName" = "myPublicIp"
"Credential" = $cred
"OpenPorts" = 3389
}
New-AzureRmVM @virtualMachineParams -ResourceGroupName $resourceGroup -Name $vmName

Просмотреть файл

@ -0,0 +1,25 @@
# Original source code: https://github.com/Azure/azure-docs-powershell-samples/blob/a513b6fceae51aaea1daaa8edd4d6fc66590d172/virtual-machine/create-vm-detailed/create-windows-vm-quick.ps1
# Variables for common values
$resourceGroup = "myResourceGroup"
$location = "westeurope"
$vmName = "myVM"
# Create user object
$cred = Get-Credential -Message "Enter a username and password for the virtual machine."
# Create a resource group
New-AzureRmResourceGroup -Name $resourceGroup -Location $location
# Create a virtual machine
# use splatted params
$virtualMachineParams = [ordered]@{
Location = $location
ImageName = "Win2016Datacenter"
VirtualNetworkName = "myVnet"
SubnetName = "mySubnet"
SecurityGroupName = "myNetworkSecurityGroup"
PublicIpAddressName = "myPublicIp"
Credential = $cred
OpenPorts = 3389
}
New-AzureRmVM @virtualMachineParams -ResourceGroupName $resourceGroup -Name $vmName

Просмотреть файл

@ -45,7 +45,6 @@ class CommandReferenceParameter
[System.String] $FileName
[System.String] $FullPath
[System.String] $Name
[System.String] $Value
[System.Int32] $StartLine
[System.Int32] $StartColumn
[System.Int32] $EndLine
@ -91,7 +90,7 @@ Enum UpgradeStepType
Enum PlanResultReasonCode
{
ReadyToUpgrade = 0
WarningSplattedParameters = 1
WarningSplattedParameters = 1 # deprecated
ErrorNoUpgradeAlias = 2
ErrorNoModuleSpecMatch = 3
ErrorParameterNotFound = 4

Просмотреть файл

@ -26,8 +26,11 @@ function Find-CmdletsInFile
)
Process
{
# constants
$matchPattern = "(\b[a-zA-z]+-[a-zA-z]+\b)"
$cmdletRegex = New-Object System.Text.RegularExpressions.Regex($matchPattern)
$doubleQuoteCharacter = '"'
$singleQuoteCharacter = ''''
$orderedTypeName = 'ordered'
# ref output vars
$parserErrors = $null
@ -47,10 +50,60 @@ function Find-CmdletsInFile
}
}
$predicate = { param($astObject) $astObject -is [System.Management.Automation.Language.CommandAst] }
# search for variable assignment statements
# the goal here is to build a table with the hastable variable sets (if any are present), to support splatted parameter names.
$recurse = $true
$assignmentPredicate = { param($astObject) $astObject -is [System.Management.Automation.Language.AssignmentStatementAst] }
$assignmentAstNodes = $rootAstNode.FindAll($assignmentPredicate, $recurse)
$hashtableVariables = New-Object -TypeName 'System.Collections.Generic.Dictionary[System.String, System.Collections.Generic.List[System.Management.Automation.Language.StringConstantExpressionAst]]'
$commandAstNodes = $rootAstNode.FindAll($predicate, $recurse)
for ([int]$i = 0; $i -lt $assignmentAstNodes.Count; $i++)
{
$currentVarAstNode = $assignmentAstNodes[$i]
# is the right hand side of the expression statement a hashtable node?
if ($currentVarAstNode.Right.Expression -is [System.Management.Automation.Language.HashtableAst])
{
# capture the hashtable variable name
$htVariableName = $currentVarAstNode.Left.VariablePath.UserPath
$hashtableVariables[$htVariableName] = New-Object -TypeName 'System.Collections.Generic.List[System.Management.Automation.Language.StringConstantExpressionAst]'
# capture the hashtable key name extents.
# -- the tuple's .Item1 contains the key name AST (which may represent a splatted parameter name).
# -- the tuple's .Item2 contains the key value AST (we dont need to capture this)
# -- also make sure to only grab hashtable key names that come from ConstantExpressionAst (to avoid unsupported subexpression keyname scenarios).
foreach ($expressionAst in $currentVarAstNode.Right.Expression.KeyValuePairs)
{
if ($expressionAst.Item1 -is [System.Management.Automation.Language.StringConstantExpressionAst])
{
$hashtableVariables[$htVariableName].Add($expressionAst.Item1)
}
}
}
elseif ($currentVarAstNode.Right.Expression -is [System.Management.Automation.Language.ConvertExpressionAst] `
-and $currentVarAstNode.Right.Expression.Type.TypeName.FullName -eq $orderedTypeName `
-and $currentVarAstNode.Right.Expression.Child -is [System.Management.Automation.Language.HashtableAst])
{
# same as the above 'if' condition case, but special handling for [ordered] hashtable objects.
# we have to check the .Child [HashtableAst] of the ConvertExpressionAst.
$htVariableName = $currentVarAstNode.Left.VariablePath.UserPath
$hashtableVariables[$htVariableName] = New-Object -TypeName 'System.Collections.Generic.List[System.Management.Automation.Language.StringConstantExpressionAst]'
foreach ($expressionAst in $currentVarAstNode.Right.Expression.Child.KeyValuePairs)
{
if ($expressionAst.Item1 -is [System.Management.Automation.Language.StringConstantExpressionAst])
{
$hashtableVariables[$htVariableName].Add($expressionAst.Item1)
}
}
}
}
# search for command statements
$commandPredicate = { param($astObject) $astObject -is [System.Management.Automation.Language.CommandAst] }
$commandAstNodes = $rootAstNode.FindAll($commandPredicate, $recurse)
$cmdletRegex = New-Object System.Text.RegularExpressions.Regex($matchPattern)
for ([int]$i = 0; $i -lt $commandAstNodes.Count; $i++)
{
@ -86,32 +139,17 @@ function Find-CmdletsInFile
{
$paramRef = New-Object -TypeName CommandReferenceParameter
# substring to cut off the dash (-) character we dont need
$paramRef.Name = $currentAstNodeCmdElement.Extent.Text.Substring(1)
# check for the parameter value.
# if this is the last element in the list, or the next item is also a
# parameter, then this parameter has no value (switch parameter)
if ($j -eq ($currentAstNode.CommandElements.Count -1) `
-or $currentAstNode.CommandElements[($j + 1)] -is [System.Management.Automation.Language.CommandParameterAst])
{
# switch param (no value)
$paramRef.Value = $null
}
else
{
# regular param (has value)
$paramRef.Value = $currentAstNode.CommandElements[($j + 1)].Extent.Text
}
# grab the parameter name with no dash value
# the extent offsets here include the dash, so add +1 to the starting values
# construct the parameter object with location details
$paramRef.Name = $currentAstNodeCmdElement.ParameterName
$paramRef.FullPath = $cmdletRef.FullPath
$paramRef.FileName = $cmdletRef.FileName
$paramRef.StartLine = $currentAstNodeCmdElement.Extent.StartLineNumber
$paramRef.StartColumn = $currentAstNodeCmdElement.Extent.StartColumnNumber
$paramRef.StartColumn = ($currentAstNodeCmdElement.Extent.StartColumnNumber + 1)
$paramRef.EndLine = $currentAstNodeCmdElement.Extent.EndLineNumber
$paramRef.EndPosition = $currentAstNodeCmdElement.Extent.EndColumnNumber
$paramRef.StartOffset = $currentAstNodeCmdElement.Extent.StartOffset
$paramRef.StartOffset = ($currentAstNodeCmdElement.Extent.StartOffset + 1)
$paramRef.EndOffset = $currentAstNodeCmdElement.Extent.EndOffset
$paramRef.Location = "{0}:{1}:{2}" -f $paramRef.FileName, $paramRef.StartLine, $paramRef.StartColumn
@ -121,6 +159,49 @@ function Find-CmdletsInFile
-and $currentAstNodeCmdElement.Splatted -eq $true)
{
$cmdletRef.HasSplattedArguments = $true
# grab the splatted parameter name without the '@' character prefix.
# we can then look this up in our known hashtable variables table.
$hashtableVariableName = $currentAstNodeCmdElement.VariablePath.UserPath
if ($hashtableVariables.ContainsKey($hashtableVariableName))
{
foreach ($splattedParameter in $hashtableVariables[$hashtableVariableName])
{
$paramRef = New-Object -TypeName CommandReferenceParameter
# add new parameter, similar to above, however a hashtable key name is the parameter name.
$paramRef.Name = $splattedParameter.Value
$paramRef.FullPath = $cmdletRef.FullPath
$paramRef.FileName = $cmdletRef.FileName
if ($splattedParameter.Extent.Text[0] -ne $doubleQuoteCharacter -and $splattedParameter.Extent.Text[0] -ne $singleQuoteCharacter)
{
# normal hash table key (not wrapped in quote characters)
$paramRef.StartLine = $splattedParameter.Extent.StartLineNumber
$paramRef.StartColumn = $splattedParameter.Extent.StartColumnNumber
$paramRef.EndLine = $splattedParameter.Extent.EndLineNumber
$paramRef.EndPosition = $splattedParameter.Extent.EndColumnNumber
$paramRef.StartOffset = $splattedParameter.Extent.StartOffset
$paramRef.EndOffset = $splattedParameter.Extent.EndOffset
}
else
{
# hash table key wrapped in quotes
# use special offset handling to account for quote wrapper characters.
$paramRef.StartLine = $splattedParameter.Extent.StartLineNumber
$paramRef.StartColumn = ($splattedParameter.Extent.StartColumnNumber + 1)
$paramRef.EndLine = $splattedParameter.Extent.EndLineNumber
$paramRef.EndPosition = ($splattedParameter.Extent.EndColumnNumber - 1)
$paramRef.StartOffset = ($splattedParameter.Extent.StartOffset + 1)
$paramRef.EndOffset = ($splattedParameter.Extent.EndOffset - 1)
}
$paramRef.Location = "{0}:{1}:{2}" -f $paramRef.FileName, $paramRef.StartLine, $paramRef.StartColumn
$cmdletRef.Parameters.Add($paramRef)
}
}
}
}
}

Просмотреть файл

@ -60,12 +60,12 @@ function Invoke-ModuleUpgradeStep
# safety check
# ensure that the file offsets are an exact match.
Confirm-StringBuilderSubstring -FileContent $FileContent -Substring ("-{0}" -f $Step.Original) `
Confirm-StringBuilderSubstring -FileContent $FileContent -Substring $Step.Original `
-StartOffset $Step.SourceCommandParameter.StartOffset -EndOffset $Step.SourceCommandParameter.EndOffset
# replacement code
$null = $FileContent.Remove($Step.SourceCommandParameter.StartOffset, ($Step.SourceCommandParameter.EndOffset - $Step.SourceCommandParameter.StartOffset));
$null = $FileContent.Insert($Step.SourceCommandParameter.StartOffset, ("-{0}" -f $Step.Replacement));
$null = $FileContent.Insert($Step.SourceCommandParameter.StartOffset, $Step.Replacement);
}
default
{

Просмотреть файл

@ -244,20 +244,10 @@ function New-AzUpgradeModulePlan
$cmdletUpgrade.StartOffset = $rmCmdlet.StartOffset
$cmdletUpgrade.Location = $rmCmdlet.Location
if ($rmCmdlet.HasSplattedArguments -eq $false)
{
$cmdletUpgrade.PlanResultReason = "Command can be automatically upgraded."
$cmdletUpgrade.PlanResult = [PlanResultReasonCode]::ReadyToUpgrade
$cmdletUpgrade.PlanSeverity = [DiagnosticSeverity]::Information
$planSteps.Add($cmdletUpgrade)
}
else
{
$cmdletUpgrade.PlanResultReason = "Cmdlet invocation uses splatted parameters. Consider unrolling to allow automated parameter upgrade checks."
$cmdletUpgrade.PlanResult = [PlanResultReasonCode]::WarningSplattedParameters
$cmdletUpgrade.PlanSeverity = [DiagnosticSeverity]::Warning
$planWarningSteps.Add($cmdletUpgrade)
}
$cmdletUpgrade.PlanResultReason = "Command can be automatically upgraded."
$cmdletUpgrade.PlanResult = [PlanResultReasonCode]::ReadyToUpgrade
$cmdletUpgrade.PlanSeverity = [DiagnosticSeverity]::Information
$planSteps.Add($cmdletUpgrade)
# check if parameters need to be updated

Просмотреть файл

@ -1,7 +0,0 @@
$splattedParams = @{
TargetName = $TargetName
Count = 5
IPv4 = $true
}
Test-Connection @splattedParams

Просмотреть файл

@ -0,0 +1,7 @@
# example 1: hashtable splatted arguments (supported)
$splattedParams = @{
TargetName = $TargetName
Count = 5
IPv4 = $true
}
Test-Connection @splattedParams -Delay 3

Просмотреть файл

@ -0,0 +1,7 @@
# example 2: hashtable splatted arguments with quote characters around key names (supported)
$splattedParams = @{
"TargetName" = $TargetName
"Count" = 5
'IPv4' = $true
}
Test-Connection @splattedParams -Delay 3

Просмотреть файл

@ -0,0 +1,10 @@
# example 3: hashtable splatted arguments with variable expressions in keynames (not supported, but should not break parser)
$keyName1 = "TargetName"
$keyName2 = "Count"
$keyName3 = "IPv4"
$splattedParams = @{
"$keyName1" = $TargetName
"$($keyName2)" = 5
$keyName3 = $true
}
Test-Connection @splattedParams -Delay 3

Просмотреть файл

@ -0,0 +1,3 @@
# example 4: array splatted arguments (not supported, but should not break parser)
$ArraySplattedArguments = "test.txt", "test2.txt"
Copy-Item @ArraySplattedArguments -WhatIf

Просмотреть файл

@ -0,0 +1,4 @@
# example 5: hashtable splatted arguments are used, but not defined in the file.
# they would be from another scope, so this scenario is also not supported.
Test-Connection @splattedParams -Delay 3

Просмотреть файл

@ -0,0 +1,7 @@
# example 1: hashtable splatted arguments with an ordered hashtable (supported)
$splattedParams = [ordered]@{
TargetName = $TargetName
Count = 5
IPv4 = $true
}
Test-Connection @splattedParams -Delay 3

Просмотреть файл

@ -29,30 +29,27 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
$results[0].Parameters.Count | Should Be 3
$results[0].Parameters[0].Name | Should Be "TargetName"
$results[0].Parameters[0].Value | Should Be "`$TargetName"
$results[0].Parameters[0].StartLine | Should Be 1
$results[0].Parameters[0].StartColumn | Should Be 17
$results[0].Parameters[0].StartColumn | Should Be 18
$results[0].Parameters[0].EndLine | Should Be 1
$results[0].Parameters[0].EndPosition | Should Be 28
$results[0].Parameters[0].StartOffset | Should Be 16
$results[0].Parameters[0].StartOffset | Should Be 17
$results[0].Parameters[0].EndOffset | Should Be 27
$results[0].Parameters[1].Name | Should Be "IPv4"
$results[0].Parameters[1].Value | Should Be ([System.String]::Empty)
$results[0].Parameters[1].StartLine | Should Be 1
$results[0].Parameters[1].StartColumn | Should Be 41
$results[0].Parameters[1].StartColumn | Should Be 42
$results[0].Parameters[1].EndLine | Should Be 1
$results[0].Parameters[1].EndPosition | Should Be 46
$results[0].Parameters[1].StartOffset | Should Be 40
$results[0].Parameters[1].StartOffset | Should Be 41
$results[0].Parameters[1].EndOffset | Should Be 45
$results[0].Parameters[2].Name | Should Be "Count"
$results[0].Parameters[2].Value | Should Be "5"
$results[0].Parameters[2].StartLine | Should Be 1
$results[0].Parameters[2].StartColumn | Should Be 47
$results[0].Parameters[2].StartColumn | Should Be 48
$results[0].Parameters[2].EndLine | Should Be 1
$results[0].Parameters[2].EndPosition | Should Be 53
$results[0].Parameters[2].StartOffset | Should Be 46
$results[0].Parameters[2].StartOffset | Should Be 47
$results[0].Parameters[2].EndOffset | Should Be 52
}
It 'Should be able to find cmdlets used in MultipleCommands script file' {
@ -77,30 +74,27 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
$results[0].Parameters.Count | Should Be 3
$results[0].Parameters[0].Name | Should Be "TargetName"
$results[0].Parameters[0].Value | Should Be "`$TargetName"
$results[0].Parameters[0].StartLine | Should Be 1
$results[0].Parameters[0].StartColumn | Should Be 17
$results[0].Parameters[0].StartColumn | Should Be 18
$results[0].Parameters[0].EndLine | Should Be 1
$results[0].Parameters[0].EndPosition | Should Be 28
$results[0].Parameters[0].StartOffset | Should Be 16
$results[0].Parameters[0].StartOffset | Should Be 17
$results[0].Parameters[0].EndOffset | Should Be 27
$results[0].Parameters[1].Name | Should Be "IPv4"
$results[0].Parameters[1].Value | Should Be ([System.String]::Empty)
$results[0].Parameters[1].StartLine | Should Be 1
$results[0].Parameters[1].StartColumn | Should Be 41
$results[0].Parameters[1].StartColumn | Should Be 42
$results[0].Parameters[1].EndLine | Should Be 1
$results[0].Parameters[1].EndPosition | Should Be 46
$results[0].Parameters[1].StartOffset | Should Be 40
$results[0].Parameters[1].StartOffset | Should Be 41
$results[0].Parameters[1].EndOffset | Should Be 45
$results[0].Parameters[2].Name | Should Be "Count"
$results[0].Parameters[2].Value | Should Be "5"
$results[0].Parameters[2].StartLine | Should Be 1
$results[0].Parameters[2].StartColumn | Should Be 47
$results[0].Parameters[2].StartColumn | Should Be 48
$results[0].Parameters[2].EndLine | Should Be 1
$results[0].Parameters[2].EndPosition | Should Be 53
$results[0].Parameters[2].StartOffset | Should Be 46
$results[0].Parameters[2].StartOffset | Should Be 47
$results[0].Parameters[2].EndOffset | Should Be 52
$results[1].StartLine | Should Be 3
@ -113,12 +107,11 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
$results[1].Parameters.Count | Should Be 1
$results[1].Parameters[0].Name | Should Be "Path"
$results[1].Parameters[0].Value | Should Be "`"C:\users\user`""
$results[1].Parameters[0].StartLine | Should Be 3
$results[1].Parameters[0].StartColumn | Should Be 15
$results[1].Parameters[0].StartColumn | Should Be 16
$results[1].Parameters[0].EndLine | Should Be 3
$results[1].Parameters[0].EndPosition | Should Be 20
$results[1].Parameters[0].StartOffset | Should Be 72
$results[1].Parameters[0].StartOffset | Should Be 73
$results[1].Parameters[0].EndOffset | Should Be 77
$results[2].StartLine | Should Be 5
@ -149,30 +142,27 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
$results[0].Parameters.Count | Should Be 3
$results[0].Parameters[0].Name | Should Be "TargetName"
$results[0].Parameters[0].Value | Should Be "`$TargetName"
$results[0].Parameters[0].StartLine | Should Be 25
$results[0].Parameters[0].StartColumn | Should Be 25
$results[0].Parameters[0].StartColumn | Should Be 26
$results[0].Parameters[0].EndLine | Should Be 25
$results[0].Parameters[0].EndPosition | Should Be 36
$results[0].Parameters[0].StartOffset | Should Be 469
$results[0].Parameters[0].StartOffset | Should Be 470
$results[0].Parameters[0].EndOffset | Should Be 480
$results[0].Parameters[1].Name | Should Be "IPv4"
$results[0].Parameters[1].Value | Should Be ([System.String]::Empty)
$results[0].Parameters[1].StartLine | Should Be 25
$results[0].Parameters[1].StartColumn | Should Be 49
$results[0].Parameters[1].StartColumn | Should Be 50
$results[0].Parameters[1].EndLine | Should Be 25
$results[0].Parameters[1].EndPosition | Should Be 54
$results[0].Parameters[1].StartOffset | Should Be 493
$results[0].Parameters[1].StartOffset | Should Be 494
$results[0].Parameters[1].EndOffset | Should Be 498
$results[0].Parameters[2].Name | Should Be "Count"
$results[0].Parameters[2].Value | Should Be "5"
$results[0].Parameters[2].StartLine | Should Be 25
$results[0].Parameters[2].StartColumn | Should Be 55
$results[0].Parameters[2].StartColumn | Should Be 56
$results[0].Parameters[2].EndLine | Should Be 25
$results[0].Parameters[2].EndPosition | Should Be 61
$results[0].Parameters[2].StartOffset | Should Be 499
$results[0].Parameters[2].StartOffset | Should Be 500
$results[0].Parameters[2].EndOffset | Should Be 505
}
It 'Should be able to find cmdlets used in MultipleCommands function file' {
@ -216,39 +206,35 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
$results[0].Parameters.Count | Should Be 4
$results[0].Parameters[0].Name | Should Be "TargetName"
$results[0].Parameters[0].Value | Should Be "`$TargetName"
$results[0].Parameters[0].StartLine | Should Be 27
$results[0].Parameters[0].StartColumn | Should Be 29
$results[0].Parameters[0].StartColumn | Should Be 30
$results[0].Parameters[0].EndLine | Should Be 27
$results[0].Parameters[0].EndPosition | Should Be 40
$results[0].Parameters[0].StartOffset | Should Be 497
$results[0].Parameters[0].StartOffset | Should Be 498
$results[0].Parameters[0].EndOffset | Should Be 508
$results[0].Parameters[1].Name | Should Be "IPv4"
$results[0].Parameters[1].Value | Should Be ([System.String]::Empty)
$results[0].Parameters[1].StartLine | Should Be 28
$results[0].Parameters[1].StartColumn | Should Be 17
$results[0].Parameters[1].StartColumn | Should Be 18
$results[0].Parameters[1].EndLine | Should Be 28
$results[0].Parameters[1].EndPosition | Should Be 22
$results[0].Parameters[1].StartOffset | Should Be 540
$results[0].Parameters[1].StartOffset | Should Be 541
$results[0].Parameters[1].EndOffset | Should Be 545
$results[0].Parameters[2].Name | Should Be "Count"
$results[0].Parameters[2].Value | Should Be "(Get-RequestCount -Test `"Value`")"
$results[0].Parameters[2].StartLine | Should Be 29
$results[0].Parameters[2].StartColumn | Should Be 17
$results[0].Parameters[2].StartColumn | Should Be 18
$results[0].Parameters[2].EndLine | Should Be 29
$results[0].Parameters[2].EndPosition | Should Be 23
$results[0].Parameters[2].StartOffset | Should Be 565
$results[0].Parameters[2].StartOffset | Should Be 566
$results[0].Parameters[2].EndOffset | Should Be 571
$results[0].Parameters[3].Name | Should Be "OriginalCommandParam"
$results[0].Parameters[3].Value | Should Be "`"Value2`""
$results[0].Parameters[3].StartLine | Should Be 30
$results[0].Parameters[3].StartColumn | Should Be 17
$results[0].Parameters[3].StartColumn | Should Be 18
$results[0].Parameters[3].EndLine | Should Be 30
$results[0].Parameters[3].EndPosition | Should Be 38
$results[0].Parameters[3].StartOffset | Should Be 624
$results[0].Parameters[3].StartOffset | Should Be 625
$results[0].Parameters[3].EndOffset | Should Be 645
$results[1].StartLine | Should Be 29
@ -263,12 +249,11 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
$results[1].Parameters.Count | Should Be 1
$results[1].Parameters[0].Name | Should Be "Test"
$results[1].Parameters[0].Value | Should Be "`"Value`""
$results[1].Parameters[0].StartLine | Should Be 29
$results[1].Parameters[0].StartColumn | Should Be 42
$results[1].Parameters[0].StartColumn | Should Be 43
$results[1].Parameters[0].EndLine | Should Be 29
$results[1].Parameters[0].EndPosition | Should Be 47
$results[1].Parameters[0].StartOffset | Should Be 590
$results[1].Parameters[0].StartOffset | Should Be 591
$results[1].Parameters[0].EndOffset | Should Be 595
}
It 'Should be able to find cmdlets used in LineContinuation script file' {
@ -364,9 +349,9 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
$results[1].CommandName | Should Be "Get-RequestCount"
$results[1].HasSplattedArguments | Should Be $false
}
It 'Should be able to find cmdlets that have splatted parameters' {
It 'Should be able to find splatted parameters' {
# arrange
$testFile = Resolve-Path -Path ".\Resources\TestFiles\ScriptExample-ParameterSplatting.ps1"
$testFile = Resolve-Path -Path ".\Resources\TestFiles\ScriptExample-ParameterSplatting1.ps1"
# act
$results = Find-CmdletsInFile -FilePath $testFile.Path
@ -374,15 +359,310 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
# assert
$results | Should Not Be Null
$results.Count | Should Be 1
$results[0].StartLine | Should Be 7
$results[0].StartColumn | Should Be 1
$results[0].EndLine | Should Be 7
$results[0].EndPosition | Should Be 16
$results[0].CommandName | Should Be "Test-Connection"
$results[0].HasSplattedArguments | Should Be $true
# we should have 4 valid parameters here, but order is not guaranteed due to enumeration
# over an unsorted dictionary. avoid using an ordered index check for the tests.
$results[0].Parameters.Count | Should Be 0
$expectedParameters = @(
[PSCustomObject]@{
Name = "TargetName"
StartLine = 3
StartColumn = 5
EndLine = 3
EndPosition = 15
StartOffset = 81
EndOffset = 91
},
[PSCustomObject]@{
Name = "Count"
StartLine = 4
StartColumn = 5
EndLine = 4
EndPosition = 10
StartOffset = 111
EndOffset = 116
},
[PSCustomObject]@{
Name = "IPv4"
StartLine = 5
StartColumn = 5
EndLine = 5
EndPosition = 9
StartOffset = 126
EndOffset = 130
},
[PSCustomObject]@{
Name = "Delay"
StartLine = 7
StartColumn = 34
EndLine = 7
EndPosition = 39
StartOffset = 176
EndOffset = 181
}
)
$results[0].Parameters.Count | Should Be $expectedParameters.Count
foreach ($expectedParam in $expectedParameters)
{
$paramSearch = $results[0].Parameters | Where-Object -FilterScript { $_.Name -eq $expectedParam.Name }
$paramSearch | Should Not Be Null
$paramSearch.StartLine | Should Be $expectedParam.StartLine
$paramSearch.StartColumn | Should Be $expectedParam.StartColumn
$paramSearch.EndLine | Should Be $expectedParam.EndLine
$paramSearch.EndPosition | Should Be $expectedParam.EndPosition
$paramSearch.StartOffset | Should Be $expectedParam.StartOffset
$paramSearch.EndOffset | Should Be $expectedParam.EndOffset
}
}
It 'Should be able to find splatted parameters from ordered hashtables' {
# arrange
$testFile = Resolve-Path -Path ".\Resources\TestFiles\ScriptExample-ParameterSplatting6.ps1"
# act
$results = Find-CmdletsInFile -FilePath $testFile.Path
# assert
$results | Should Not Be Null
$results.Count | Should Be 1
$results[0].StartLine | Should Be 7
$results[0].StartColumn | Should Be 1
$results[0].EndLine | Should Be 7
$results[0].EndPosition | Should Be 16
$results[0].CommandName | Should Be "Test-Connection"
$results[0].HasSplattedArguments | Should Be $true
# we should have 4 valid parameters here, but order is not guaranteed due to enumeration
# over an unsorted dictionary. avoid using an ordered index check for the tests.
$expectedParameters = @(
[PSCustomObject]@{
Name = "TargetName"
StartLine = 3
StartColumn = 5
EndLine = 3
EndPosition = 15
StartOffset = 116
EndOffset = 126
},
[PSCustomObject]@{
Name = "Count"
StartLine = 4
StartColumn = 5
EndLine = 4
EndPosition = 10
StartOffset = 146
EndOffset = 151
},
[PSCustomObject]@{
Name = "IPv4"
StartLine = 5
StartColumn = 5
EndLine = 5
EndPosition = 9
StartOffset = 161
EndOffset = 165
},
[PSCustomObject]@{
Name = "Delay"
StartLine = 7
StartColumn = 34
EndLine = 7
EndPosition = 39
StartOffset = 211
EndOffset = 216
}
)
$results[0].Parameters.Count | Should Be $expectedParameters.Count
foreach ($expectedParam in $expectedParameters)
{
$paramSearch = $results[0].Parameters | Where-Object -FilterScript { $_.Name -eq $expectedParam.Name }
$paramSearch | Should Not Be Null
$paramSearch.StartLine | Should Be $expectedParam.StartLine
$paramSearch.StartColumn | Should Be $expectedParam.StartColumn
$paramSearch.EndLine | Should Be $expectedParam.EndLine
$paramSearch.EndPosition | Should Be $expectedParam.EndPosition
$paramSearch.StartOffset | Should Be $expectedParam.StartOffset
$paramSearch.EndOffset | Should Be $expectedParam.EndOffset
}
}
It 'Should be able to find splatted parameters wrapped with quote characters' {
# arrange
$testFile = Resolve-Path -Path ".\Resources\TestFiles\ScriptExample-ParameterSplatting2.ps1"
# act
$results = Find-CmdletsInFile -FilePath $testFile.Path
# assert
$results | Should Not Be Null
$results.Count | Should Be 1
$results[0].StartLine | Should Be 7
$results[0].StartColumn | Should Be 1
$results[0].EndLine | Should Be 7
$results[0].EndPosition | Should Be 16
$results[0].CommandName | Should Be "Test-Connection"
$results[0].HasSplattedArguments | Should Be $true
# we should have 4 valid parameters here, but order is not guaranteed due to enumeration
# over an unsorted dictionary. avoid using an ordered index check for the tests.
# difference here over the first example is the addition of quote characters.
# we want to make sure we index/offset at the right locations to account for the quotes.
$expectedParameters = @(
[PSCustomObject]@{
Name = "TargetName"
StartLine = 3
StartColumn = 6
EndLine = 3
EndPosition = 16
StartOffset = 121
EndOffset = 131
},
[PSCustomObject]@{
Name = "Count"
StartLine = 4
StartColumn = 6
EndLine = 4
EndPosition = 11
StartOffset = 153
EndOffset = 158
},
[PSCustomObject]@{
Name = "IPv4"
StartLine = 5
StartColumn = 6
EndLine = 5
EndPosition = 10
StartOffset = 170
EndOffset = 174
},
[PSCustomObject]@{
Name = "Delay"
StartLine = 7
StartColumn = 34
EndLine = 7
EndPosition = 39
StartOffset = 221
EndOffset = 226
}
)
$results[0].Parameters.Count | Should Be $expectedParameters.Count
foreach ($expectedParam in $expectedParameters)
{
$paramSearch = $results[0].Parameters | Where-Object -FilterScript { $_.Name -eq $expectedParam.Name }
$paramSearch | Should Not Be Null
$paramSearch.StartLine | Should Be $expectedParam.StartLine
$paramSearch.StartColumn | Should Be $expectedParam.StartColumn
$paramSearch.EndLine | Should Be $expectedParam.EndLine
$paramSearch.EndPosition | Should Be $expectedParam.EndPosition
$paramSearch.StartOffset | Should Be $expectedParam.StartOffset
$paramSearch.EndOffset | Should Be $expectedParam.EndOffset
}
}
It 'Should not detect splatted parameter key names defined using expressions' {
# arrange
$testFile = Resolve-Path -Path ".\Resources\TestFiles\ScriptExample-ParameterSplatting3.ps1"
# act
$results = Find-CmdletsInFile -FilePath $testFile.Path
# assert
$results | Should Not Be Null
$results.Count | Should Be 1
$results[0].StartLine | Should Be 10
$results[0].StartColumn | Should Be 1
$results[0].EndLine | Should Be 10
$results[0].EndPosition | Should Be 16
$results[0].CommandName | Should Be "Test-Connection"
# this scenario uses key names determined by other expressions.
# this is out of scope/unsupported, but should not break the parser.
# it should indicate that we have splatted arguments, but only detect the single valid parameter.
$results[0].HasSplattedArguments | Should Be $true
$results[0].Parameters.Count | Should Be 1
$results[0].Parameters[0].Name | Should Be "Delay"
$results[0].Parameters[0].StartLine | Should Be 10
$results[0].Parameters[0].StartColumn | Should Be 34
$results[0].Parameters[0].EndLine | Should Be 10
$results[0].Parameters[0].EndPosition | Should Be 39
$results[0].Parameters[0].StartOffset | Should Be 329
$results[0].Parameters[0].EndOffset | Should Be 334
}
It 'Should not detect positional argument array splatted arguments' {
# arrange
$testFile = Resolve-Path -Path ".\Resources\TestFiles\ScriptExample-ParameterSplatting4.ps1"
# act
$results = Find-CmdletsInFile -FilePath $testFile.Path
# assert
$results | Should Not Be Null
$results.Count | Should Be 1
$results[0].StartLine | Should Be 3
$results[0].StartColumn | Should Be 1
$results[0].EndLine | Should Be 3
$results[0].EndPosition | Should Be 10
$results[0].CommandName | Should Be "Copy-Item"
# this scenario uses positional argument arrays as splatted parameters.
# these are positional arguments (unsupported because they have no key names).
# however it should indicate that we have splatted arguments and only detect the single valid parameter.
$results[0].HasSplattedArguments | Should Be $true
$results[0].Parameters.Count | Should Be 1
$results[0].Parameters[0].Name | Should Be "WhatIf"
$results[0].Parameters[0].StartLine | Should Be 3
$results[0].Parameters[0].StartColumn | Should Be 36
$results[0].Parameters[0].EndLine | Should Be 3
$results[0].Parameters[0].EndPosition | Should Be 42
$results[0].Parameters[0].StartOffset | Should Be 170
$results[0].Parameters[0].EndOffset | Should Be 176
}
It 'Should not detect splatted arguments defined outside the file scope' {
# arrange
$testFile = Resolve-Path -Path ".\Resources\TestFiles\ScriptExample-ParameterSplatting5.ps1"
# act
$results = Find-CmdletsInFile -FilePath $testFile.Path
# assert
$results | Should Not Be Null
$results.Count | Should Be 1
$results[0].StartLine | Should Be 4
$results[0].StartColumn | Should Be 1
$results[0].EndLine | Should Be 4
$results[0].EndPosition | Should Be 16
$results[0].CommandName | Should Be "Test-Connection"
# this scenario references a hashtable argument that doesn't exist in the scope of the file.
# this obviously isn't supported because we can't expand the search beyond the scope of the file.
# however it should indicate that we have splatted arguments and only detect the single valid parameter.
$results[0].HasSplattedArguments | Should Be $true
$results[0].Parameters.Count | Should Be 1
$results[0].Parameters[0].Name | Should Be "Delay"
$results[0].Parameters[0].StartLine | Should Be 4
$results[0].Parameters[0].StartColumn | Should Be 34
$results[0].Parameters[0].EndLine | Should Be 4
$results[0].Parameters[0].EndPosition | Should Be 39
$results[0].Parameters[0].StartOffset | Should Be 194
$results[0].Parameters[0].EndOffset | Should Be 199
}
}
}

Просмотреть файл

@ -55,7 +55,7 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
$step.Location = "test.ps1:3:5"
$step.SourceCommandParameter = New-Object -TypeName CommandReferenceParameter
$step.SourceCommandParameter.StartOffset = 80
$step.SourceCommandParameter.StartOffset = 81
$step.SourceCommandParameter.EndOffset = 85
# act
@ -105,7 +105,7 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
$step.Location = "test.ps1:3:5"
$step.SourceCommandParameter = New-Object -TypeName CommandReferenceParameter
$step.SourceCommandParameter.StartOffset = 80
$step.SourceCommandParameter.StartOffset = 81
$step.SourceCommandParameter.EndOffset = 85
# act

Просмотреть файл

@ -179,54 +179,6 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
Assert-VerifiableMock
}
It 'Should be able to execute plan steps with warning state' {
# arrange
$step1 = New-Object -TypeName UpgradePlan
$step1.FullPath = "C:\mock-file.ps1"
$step1.UpgradeType = [UpgradeStepType]::Cmdlet
$step1.PlanResult = [PlanResultReasonCode]::ReadyToUpgrade
$step1.PlanSeverity = [DiagnosticSeverity]::Information
$step1.Location = "mocked-file.ps1:10:5"
$step1.Original = "Login-AzureRmAccount"
$step1.Replacement = "Login-AzAccount"
$step2 = New-Object -TypeName UpgradePlan
$step2.FullPath = "C:\mock-file.ps1"
$step2.UpgradeType = [UpgradeStepType]::Cmdlet
$step2.PlanResult = [PlanResultReasonCode]::WarningSplattedParameters
$step2.PlanSeverity = [DiagnosticSeverity]::Warning
$step2.Location = "mocked-file.ps1:20:1"
$step2.Original = "Get-AzureRmCommandThatIsUsingSplattedParameters"
$step2.Replacement = "Get-AzCommandThatIsUsingSplattedParameters" # has a replacement, but is in warning state.
$plan = @( $step1, $step2 )
Mock -CommandName Invoke-ModuleUpgradeStep -ModuleName Az.Tools.Migration -MockWith { } -Verifiable
Mock -CommandName Get-Content -MockWith { return "mock-file-contents" } -Verifiable
Mock -CommandName Set-Content -MockWith { }
# ensure we don't send telemetry during tests.
Mock -CommandName Send-MetricsIfDataCollectionEnabled -ModuleName Az.Tools.Migration -MockWith { }
# act
$results = Invoke-AzUpgradeModulePlan -Plan $plan -FileEditMode ModifyExistingFiles -Confirm:$false
# assert
$results | Should Not Be $null
$results.Count | Should Be 2
# first plan step should have upgraded fine.
$results[0].GetType().FullName | Should Be 'UpgradeResult'
$results[0].UpgradeResult.ToString() | Should Be 'UpgradeCompleted'
$results[0].UpgradeSeverity.ToString() | Should Be 'Information'
# second plan step should be executed, but return a Completed w/ warnings state.
$results[1].GetType().FullName | Should Be 'UpgradeResult'
$results[1].UpgradeResult.ToString() | Should Be 'UpgradedWithWarnings'
$results[1].UpgradeSeverity.ToString() | Should Be 'Warning'
Assert-VerifiableMock
}
It 'Should be able to handle file upgrade errors' {
# arrange
$step1 = New-Object -TypeName UpgradePlan

Просмотреть файл

@ -41,7 +41,6 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
$cmdlet1Param = New-Object -TypeName CommandReferenceParameter
$cmdlet1Param.Name = "EnvironmentName"
$cmdlet1Param.Value = "mock-value"
$cmdlet1Param.StartOffset = 27
$cmdlet1.Parameters.Add($cmdlet1Param)
@ -234,34 +233,6 @@ InModuleScope -ModuleName Az.Tools.Migration -ScriptBlock {
$results[8].Replacement | Should Be 'Login-AzAccount'
$results[8].StartOffset | Should Be 33
}
It 'Should be able to generate warnings for splatted parameter scenarios' {
# arrange
$cmdlet1 = New-Object -TypeName CommandReference
$cmdlet1.FileName = "mock-file.ps1"
$cmdlet1.FullPath = "C:\mock-file.ps1"
$cmdlet1.CommandName = "Login-AzureRmAccount"
$cmdlet1.StartOffset = 10
$cmdlet1.HasSplattedArguments = $true
$foundCmdlets = @()
$foundCmdlets += $cmdlet1
# ensure we don't send telemetry during tests.
Mock -CommandName Send-MetricsIfDataCollectionEnabled -ModuleName Az.Tools.Migration -MockWith { }
# act
# should generate a warning, and an upgrade step
$results = New-AzUpgradeModulePlan -AzureRmCmdReference $foundCmdlets -ToAzVersion 4.8.0
# assert
$results | Should Not Be $null
$results.Count | Should Be 1
$results.UpgradeType.ToString() | Should Be 'Cmdlet'
$results.PlanResult.ToString() | Should Be "WarningSplattedParameters"
$results.PlanSeverity.ToString() | Should Be 'Warning'
$results.PlanResultReason.Contains("splatted parameters") | Should Be $true
}
It 'Should be able to generate errors for source cmdlets missing upgrade aliases' {
# arrange
$cmdlet1 = New-Object -TypeName CommandReference