From 9a987dca6e1091cd3e484be0d68ad1d7ba975170 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:54:45 +0000 Subject: [PATCH 1/2] Initial plan From 60f358acf3aef4d9af01724692052c78286244ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:01:18 +0000 Subject: [PATCH 2/2] Fix AvoidAlias DSC review feedback Co-authored-by: bergmeister <9250262+bergmeister@users.noreply.github.com> --- Rules/AvoidAlias.cs | 74 ++++++++++++++++++++++++++- Tests/Rules/AvoidUsingAlias.tests.ps1 | 39 ++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/Rules/AvoidAlias.cs b/Rules/AvoidAlias.cs index 3fb623f29..d59920db4 100644 --- a/Rules/AvoidAlias.cs +++ b/Rules/AvoidAlias.cs @@ -195,7 +195,25 @@ private bool IgnoreCommandast(CommandAst cmdAst) private bool IsDscResourceDeclaration(CommandAst cmdAst) { - if (GetDscResourceBody(cmdAst) == null) + bool hasConfigurationAncestor = false; + for (Ast parent = cmdAst.Parent; parent != null; parent = parent.Parent) + { + if (parent is ConfigurationDefinitionAst) + { + hasConfigurationAncestor = true; + break; + } + + if (parent is FunctionDefinitionAst + || parent is HashtableAst + || parent is AssignmentStatementAst + || parent is ParenExpressionAst) + { + return false; + } + } + + if (!hasConfigurationAncestor || GetDscResourceBody(cmdAst) == null) { return false; } @@ -207,7 +225,17 @@ private bool IsDscResourceDeclaration(CommandAst cmdAst) return true; } - if (parent is FunctionDefinitionAst || parent is HashtableAst) + if (parent is FunctionDefinitionAst + || parent is HashtableAst + || parent is AssignmentStatementAst + || parent is ParenExpressionAst) + { + return false; + } + + if (parent is CommandExpressionAst commandExpression + && (!(commandExpression.Expression is ScriptBlockExpressionAst body) + || !IsFollowingLineDscResourceBody(body))) { return false; } @@ -236,6 +264,41 @@ private bool IsDscResourceDeclaration(CommandAst cmdAst) return false; } + private bool IsFollowingLineDscResourceBody(ScriptBlockExpressionAst body) + { + if (!(body.Parent is CommandExpressionAst bodyExpression) + || !(bodyExpression.Parent is PipelineAst bodyPipeline) + || bodyPipeline.PipelineElements.Count != 1 + || bodyPipeline.PipelineElements[0] != bodyExpression) + { + return false; + } + + Ast statement = bodyPipeline; + for (Ast parent = bodyPipeline.Parent; parent != null; parent = parent.Parent) + { + var statements = (parent as NamedBlockAst)?.Statements + ?? (parent as StatementBlockAst)?.Statements; + if (statements == null) + { + statement = parent; + continue; + } + + int index = statements.IndexOf(statement as StatementAst); + if (index <= 0 || !(statements[index - 1] is PipelineAst declarationPipeline) + || declarationPipeline.PipelineElements.Count != 1 + || !(declarationPipeline.PipelineElements[0] is CommandAst declaration)) + { + return false; + } + + return GetDscResourceBody(declaration) == body; + } + + return false; + } + private ScriptBlockExpressionAst GetDscResourceBody(CommandAst cmdAst) { if (cmdAst.InvocationOperator != TokenKind.Unknown @@ -246,6 +309,13 @@ private ScriptBlockExpressionAst GetDscResourceBody(CommandAst cmdAst) return null; } + if (!(cmdAst.Parent is PipelineAst commandPipeline) + || commandPipeline.PipelineElements.Count != 1 + || commandPipeline.PipelineElements[0] != cmdAst) + { + return null; + } + if (cmdAst.CommandElements.Count == 3) { return cmdAst.CommandElements[2] as ScriptBlockExpressionAst; diff --git a/Tests/Rules/AvoidUsingAlias.tests.ps1 b/Tests/Rules/AvoidUsingAlias.tests.ps1 index e9b89a02f..bae7310a7 100644 --- a/Tests/Rules/AvoidUsingAlias.tests.ps1 +++ b/Tests/Rules/AvoidUsingAlias.tests.ps1 @@ -146,6 +146,45 @@ Configuration PackageTest { $violations[5].Extent.Text | Should -Be 'Verb' $violations[6].Extent.Text | Should -Be 'Verb' } + + It "does not ignore executable commands in assignment and expression contexts or pipelines" { + $target = @' +{ + $items = ChildItem . {} + $result = Verb Argument {} + $action = { Verb Argument {} } + Name = (Verb Argument {}) + Script ScriptExample { + SetScript = + { + Verb Argument {} + } + } + Verb Argument {} | Out-Null + gci . | Out-Null + { + } +} +'@ + $ast = [System.Management.Automation.Language.Parser]::ParseInput($target, [ref]$null, [ref]$null) + $body = $ast.EndBlock.Statements[0].PipelineElements[0].Expression.Copy() + $configuration = [System.Management.Automation.Language.ConfigurationDefinitionAst]::new( + $ast.Extent, $body, 'Resource', + [System.Management.Automation.Language.StringConstantExpressionAst]::new($ast.Extent, 'Test', 'BareWord')) + $rule = [Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.AvoidAlias]::new() + + $violations = @($rule.AnalyzeScript($configuration, $null)) + + $violations.Count | Should -Be 7 + $violations[0].Extent.Text | Should -Be 'ChildItem' + $violations[0].SuggestedCorrections[0].Text | Should -Be 'Get-ChildItem' + $violations[1].Extent.Text | Should -Be 'Verb' + $violations[2].Extent.Text | Should -Be 'Verb' + $violations[3].Extent.Text | Should -Be 'Verb' + $violations[4].Extent.Text | Should -Be 'Verb' + $violations[5].Extent.Text | Should -Be 'Verb' + $violations[6].Extent.Text | Should -Be 'gci' + } } Context "Settings file provides allowlist" {