| | | 1 | | <# |
| | | 2 | | .SYNOPSIS |
| | | 3 | | Placeholder function to indicate no default value. |
| | | 4 | | .DESCRIPTION |
| | | 5 | | This function serves as a marker to indicate that no default value is provided for a parameter. |
| | | 6 | | It returns $null when invoked. |
| | | 7 | | When used in parameter declarations, it allows the caller to distinguish between an explicit |
| | | 8 | | default value and the absence of a default. |
| | | 9 | | .PARAMETER Value |
| | | 10 | | An optional value to return instead of the sentinel indicating no default. |
| | | 11 | | If provided, this value is returned immediately. |
| | | 12 | | .EXAMPLE |
| | | 13 | | Usage example: |
| | | 14 | | function Test-Function { |
| | | 15 | | param( |
| | | 16 | | [datetime]$DateParam = (NoDefault) |
| | | 17 | | ) |
| | | 18 | | if ($DateParam -eq [datetime]::MinValue) { |
| | | 19 | | Write-Output "No default provided for DateParam." |
| | | 20 | | } else { |
| | | 21 | | Write-Output "DateParam has value: $DateParam" |
| | | 22 | | } |
| | | 23 | | } |
| | | 24 | | .NOTES |
| | | 25 | | When a parameter is declared with NoDefault as its default value, the function inspects |
| | | 26 | | the call stack to determine the static type of the parameter. If the type is a nullable |
| | | 27 | | type or a reference type, it returns $null. For non-nullable value types, it returns a sentinel |
| | | 28 | | value (e.g., [datetime]::MinValue for [datetime]) that can be detected by the caller. |
| | | 29 | | This allows functions to differentiate between parameters that have no default and those |
| | | 30 | | that have an explicit default value. |
| | | 31 | | .OUTPUTS |
| | | 32 | | Returns $null. |
| | | 33 | | #> |
| | | 34 | | function NoDefault { |
| | | 35 | | param( |
| | | 36 | | [object] $Value = $null |
| | | 37 | | ) |
| | | 38 | | |
| | | 39 | | # If caller provided a real default, return it immediately. |
| | | 40 | | # Usage: [datetime]$x = (NoDefault ([datetime]'2026-01-01')) |
| | 0 | 41 | | if ($PSBoundParameters.ContainsKey('Value')) { |
| | 0 | 42 | | return $Value |
| | | 43 | | } |
| | | 44 | | |
| | 0 | 45 | | $call = (Get-PSCallStack)[1] |
| | 0 | 46 | | if (-not $call.ScriptName) { return $null } |
| | | 47 | | |
| | 0 | 48 | | $tokens = $null |
| | 0 | 49 | | $errors = $null |
| | 0 | 50 | | $ast = [System.Management.Automation.Language.Parser]::ParseFile( |
| | | 51 | | $call.ScriptName, [ref]$tokens, [ref]$errors |
| | | 52 | | ) |
| | | 53 | | |
| | 0 | 54 | | $line = $call.ScriptLineNumber |
| | | 55 | | |
| | | 56 | | # Find the assignment ON THIS LINE whose RHS is NoDefault / (NoDefault) / $(NoDefault) |
| | 0 | 57 | | $assign = $ast.FindAll({ |
| | | 58 | | param($a) |
| | 0 | 59 | | $a -is [System.Management.Automation.Language.AssignmentStatementAst] -and |
| | | 60 | | $a.Extent.StartLineNumber -le $line -and |
| | | 61 | | $a.Extent.EndLineNumber -ge $line -and |
| | 0 | 62 | | ($a.Right.Extent.Text -match '^\s*\(?\s*NoDefault\b') |
| | 0 | 63 | | }, $true) | Select-Object -First 1 |
| | | 64 | | |
| | 0 | 65 | | if (-not $assign) { return $null } |
| | | 66 | | |
| | | 67 | | # In script scope, LHS is typically an AttributedExpressionAst |
| | 0 | 68 | | $lhs = $assign.Left |
| | | 69 | | |
| | | 70 | | # Prefer the inner attributed child if present (matches what you printed) |
| | 0 | 71 | | $attrib = $lhs.Child |
| | 0 | 72 | | if ($attrib -isnot [System.Management.Automation.Language.AttributedExpressionAst]) { |
| | | 73 | | # fallback: maybe LHS itself is attributed |
| | 0 | 74 | | if ($lhs -is [System.Management.Automation.Language.AttributedExpressionAst]) { |
| | 0 | 75 | | $attrib = $lhs |
| | | 76 | | } else { |
| | 0 | 77 | | return $null |
| | | 78 | | } |
| | | 79 | | } |
| | | 80 | | |
| | 0 | 81 | | switch ($attrib.Attribute.TypeName.name) { |
| | | 82 | | 'ValidateSet' { |
| | 0 | 83 | | $defaultNull = ($attrib.Attribute.Extent.Text -replace ".*ValidateSet\(|['\)]", '').Split(',')[0].Trim() |
| | 0 | 84 | | $attrib = $attrib.Child |
| | | 85 | | break |
| | | 86 | | } |
| | | 87 | | 'ValidateRange' { |
| | 0 | 88 | | $defaultNull = ($attrib.Attribute.Extent.Text -replace '.*ValidateRange\(|[\]]', '').Split(',')[0].Trim() |
| | 0 | 89 | | $attrib = $attrib.Child |
| | | 90 | | break |
| | | 91 | | } |
| | 0 | 92 | | { $_ -in 'ValidateNotNull', 'ValidateNotNullOrWhiteSpace', 'ValidateNotNullOrEmpty' } { |
| | 0 | 93 | | $defaultNull = '__FAKE_NULL__' |
| | 0 | 94 | | $attrib = $attrib.Child |
| | | 95 | | break |
| | | 96 | | } |
| | 0 | 97 | | default { $defaultNull = $null } |
| | | 98 | | } |
| | | 99 | | |
| | 0 | 100 | | $t = $attrib.StaticType |
| | | 101 | | |
| | | 102 | | # Nullable<T> => return $null |
| | 0 | 103 | | if ($t.IsGenericType -and $t.GetGenericTypeDefinition() -eq [Nullable`1]) { |
| | 0 | 104 | | return $defaultNull |
| | | 105 | | } |
| | | 106 | | |
| | | 107 | | # Reference types => return $null |
| | 0 | 108 | | if (-not $t.IsValueType) { |
| | 0 | 109 | | return $defaultNull |
| | | 110 | | } |
| | | 111 | | # Nullable reference types => return $null |
| | 0 | 112 | | if ($null -ne $defaultNull) { |
| | 0 | 113 | | return $defaultNull |
| | | 114 | | } |
| | | 115 | | # Non-nullable value types => return a sentinel you can detect later |
| | 0 | 116 | | switch ($t.FullName) { |
| | 0 | 117 | | 'System.DateTime' { return [datetime]::MinValue } |
| | 0 | 118 | | 'System.DateTimeOffset' { return [datetimeoffset]::MinValue } |
| | 0 | 119 | | 'System.Guid' { return [guid]::Empty } |
| | 0 | 120 | | 'System.TimeSpan' { return [timespan]::Zero } |
| | | 121 | | default { |
| | | 122 | | # int => 0, bool => false, etc. |
| | 0 | 123 | | return [Activator]::CreateInstance($t) |
| | | 124 | | } |
| | | 125 | | } |
| | | 126 | | } |
| | | 127 | | |