diff --git a/.github/prompts/update-synopsys.prompt.md b/.github/prompts/update-synopsys.prompt.md new file mode 100644 index 0000000..0c5a4be --- /dev/null +++ b/.github/prompts/update-synopsys.prompt.md @@ -0,0 +1,166 @@ +--- +description: "Review and update PowerShell function comment-based help to ensure complete and accurate documentation" +agent: "agent" +model: "GPT-5.2" +--- + +# Update PowerShell Function Synopsis + +Review and update the comment-based help block for all PowerShell functions in this repository. Ensure that every file in scope (all public and private function scripts) is processed until completion. For each function, the comment-based help block (synopsis block) must be placed immediately after the function name and before the param block, inside the function definition, to comply with project conventions. All other requirements below must also be met. Do not stop until every file is compliant. + +## Formatting and Style + +- Use consistent indentation (4 spaces) for all content under each help tag (e.g., .SYNOPSIS, .DESCRIPTION, .PARAMETER, etc.). +- Place a blank line between each major help tag (e.g., between .SYNOPSIS and .DESCRIPTION, between .DESCRIPTION and .PARAMETER, etc.). +- For .PARAMETER and .EXAMPLE sections, keep the content readable and aligned, following the style of existing well-formatted help blocks in the codebase. +- Do not compress help blocks into a single paragraph; preserve logical line breaks and paragraph structure for clarity. +- Use the original style as seen in existing compliant files (such as Get-DSFileInventory.ps1) as the standard for all future help blocks. + +## Requirements + +All public PowerShell functions must have complete comment-based help that includes.prompts for the following sections: + +### 1. .SYNOPSIS + +- Must be present +- Should be a brief, one-line description of what the function does +- Keep it concise (typically under 100 characters) +- Use active voice and start with a verb + +### 2. .DESCRIPTION + +- Must be present +- Must be longer than 40 characters +- Should provide detailed explanation of the function's purpose and behavior +- Explain what the function does, not how it does it +- Include any important context or prerequisites + +### 3. .EXAMPLE + +- Must have at least one example +- Should demonstrate the most common use case first +- Include example output or results when helpful +- Add multiple examples for complex functions showing different scenarios +- Each example should have a description explaining what it does + +### 4. .PARAMETER + +- Must describe all parameters +- Each parameter description should explain: + - What the parameter does + - Valid values or format expected + - Whether it accepts pipeline input + - Any default values if applicable + +## Example Format + +```powershell +<# +.SYNOPSIS + Gets the sanitization configuration for the current session. + +.DESCRIPTION + The Get-DSConfig function retrieves the current DataSanitizer configuration + settings that control how data detection and sanitization operations are performed. + This includes detection rules, file paths, and processing options. + +.PARAMETER Path + The path to the configuration file. If not specified, uses the default + configuration location in the module directory. + +.PARAMETER Detailed + Returns additional configuration metadata including load time and source file. + +.EXAMPLE + Get-DSConfig + + Retrieves the current configuration using default settings. + +.EXAMPLE + Get-DSConfig -Path "C:\Custom\config.json" + + Loads configuration from a custom location. + +.EXAMPLE + Get-DSConfig -Detailed + + Returns the configuration with additional metadata about when and where + it was loaded from. + +.OUTPUTS + PSCustomObject + Returns a custom object containing the configuration settings. + +.NOTES + This function is typically called automatically during module initialization. +#> +``` + +## Validation Checklist + +Before completing the update, verify: + +- [ ] `.SYNOPSIS` exists and is concise +- [ ] `.DESCRIPTION` exists and is longer than 40 characters +- [ ] At least one `.EXAMPLE` is provided +- [ ] All function parameters have `.PARAMETER` descriptions +- [ ] Examples show realistic usage scenarios +- [ ] Parameter descriptions explain purpose and expected values +- [ ] Help text follows PowerShell conventions + +## Accuracy Review + +**CRITICAL**: Verify that existing help documentation matches the current code implementation: + +- [ ] **Parameter list matches**: All parameters in the function signature have `.PARAMETER` entries +- [ ] **No orphaned parameters**: Remove `.PARAMETER` entries for parameters that no longer exist +- [ ] **Parameter types accurate**: Verify type declarations match the actual parameter types +- [ ] **Default values current**: Update any mentioned default values to match code +- [ ] **Mandatory status correct**: Document which parameters are mandatory vs optional +- [ ] **Pipeline support accurate**: Verify `ValueFromPipeline` and `ValueFromPipelineByPropertyName` claims +- [ ] **Return type matches**: `.OUTPUTS` section reflects what the function actually returns +- [ ] **Examples work**: Test each example to ensure it runs without errors +- [ ] **Behavior described correctly**: `.DESCRIPTION` accurately reflects current function logic +- [ ] **WhatIf/Confirm support**: Document if function supports `-WhatIf` and `-Confirm` + +### Common Issues to Check + +1. **Parameters added/removed**: Code may have been refactored without updating help +2. **Parameter renamed**: Old parameter names in examples or descriptions +3. **Changed default values**: Help mentions old defaults +4. **Modified behavior**: Description doesn't match current implementation +5. **Broken examples**: Examples use outdated syntax or removed features +6. **Missing new features**: Recent functionality not documented +7. **Incorrect pipeline behavior**: Help claims don't match actual implementation + +## Additional Best Practices + +- Use `.OUTPUTS` to document the type of object returned +- Ensure help text is grammatically correct and professional +- Test help display with `Get-Help -Full` + +## Files to Review + +Review **all** functions in both public and private directories: + +- `source/Public/**/*.ps1` - Exported functions used by module consumers (critical) +- `source/Private/**/*.ps1` - Internal functions used within the module + +### Documentation Priority + +**Public Functions** (highest priority): + +- Complete, detailed documentation is critical +- These are exported and used by external consumers +- Examples must be comprehensive and realistic +- All parameters must be thoroughly documented + +**Private Functions** (still important): + +- Complete documentation helps maintainability +- Assists other developers working on the module +- May be less detailed than public functions but still required +- Examples can focus on internal use cases +- Helps with code understanding and refactoring + +Both public and private functions must meet all requirements, but public functions should have more comprehensive examples and descriptions since they form the module's public API. diff --git a/.gitignore b/.gitignore index e2231a3..cdf0aff 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ node_modules package-lock.json **/*nosync*/** + +.automatedlab/ diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2991140..9801d89 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -83,8 +83,53 @@ { "label": "test", "type": "shell", + "command": "&${cwd}/.automatedlab/automatedlab.ps1", + "args": [], + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "dedicated", + "showReuseMessage": true, + "clear": false + }, + "problemMatcher": [ + { + "owner": "powershell", + "fileLocation": [ + "absolute" + ], + "severity": "error", + "pattern": [ + { + "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", + "message": 1 + }, + { + "regexp": "(.*)", + "code": 1 + }, + { + "regexp": "" + }, + { + "regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*", + "file": 1, + "line": 2 + } + ] + } + ] + }, + { + "label": "pester", + "type": "shell", "command": "&${cwd}/build.ps1", - "args": ["-AutoRestore","-Tasks","test"], + "args": [ + "-AutoRestore", + "-Tasks", + "test" + ], "presentation": { "echo": true, "reveal": "always", @@ -122,4 +167,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index aafa8a2..479433e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Powershell module initialization using the excellent [Sampler](https://github.com/gaelcolas/Sampler). +- Add 'ResourceLogCategory' Class - `Import-CsEnvironment`: Import settings and environments from JSON/JSONC files to configure PowerShell environments easily. - `Write-OutputPadded`: Enhance output readability by formatting it with customizable padding and styling options. - `Set-CsConfig`: Create or update configuration files to adjust PowerShell environment settings as needed. diff --git a/GitVersion.yml b/GitVersion.yml index 9dc1c61..5eb3e93 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -5,6 +5,7 @@ minor-version-bump-message: '(adds?|features?|minor)\b' patch-version-bump-message: '\s?(fix|patch)' no-bump-message: '\+semver:\s?(none|skip)' assembly-informational-format: "{NuGetVersionV2}+Sha.{Sha}.Date.{CommitDate}" +commit-message-incrementing: MergeMessageOnly branches: main: tag: preview diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index f2e0231..6a68db2 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -12,15 +12,18 @@ # } #} - InvokeBuild = 'latest' - PSScriptAnalyzer = 'latest' - Pester = 'latest' - ModuleBuilder = 'latest' - ChangelogManagement = 'latest' - Sampler = 'latest' - 'Sampler.GitHubTasks' = 'latest' - 'DscResource.DocGenerator' = 'latest' - PlatyPS = 'latest' + InvokeBuild = 'latest' + PSScriptAnalyzer = 'latest' + Pester = 'latest' + ModuleBuilder = 'latest' + ChangelogManagement = 'latest' + Sampler = 'latest' + 'Sampler.GitHubTasks' = 'latest' + 'DscResource.DocGenerator' = 'latest' + PlatyPS = 'latest' + 'Az.Accounts' = 'latest' + 'Az.Monitor' = 'latest' + 'Az.Resources' = 'latest' } diff --git a/Resolve-Dependency.psd1 b/Resolve-Dependency.psd1 index c72178d..c6e5306 100644 --- a/Resolve-Dependency.psd1 +++ b/Resolve-Dependency.psd1 @@ -30,7 +30,7 @@ #AllowOldPowerShellGetModule = $true #MinimumPSDependVersion = '0.3.0' - AllowPrerelease = $false + AllowPrerelease = $true WithYAML = $true # Will also bootstrap PowerShell-Yaml to read other config files <# @@ -57,9 +57,9 @@ script and correct parameter values. This will also affect the use of parameter `-UseModuleFast` of the Resolve-Dependency.ps1 or build.ps1 script. #> - #UseModuleFast = $true - #ModuleFastVersion = '0.1.2' - #ModuleFastBleedingEdge = $true + UseModuleFast = $true + ModuleFastVersion = '0.6.0' + ModuleFastBleedingEdge = $true <# Enable PSResourceGet to be the default method of resolving dependencies by setting @@ -67,10 +67,10 @@ set to $false then PowerShellGet will be used to resolve dependencies. #> UsePSResourceGet = $true - PSResourceGetVersion = '1.0.1' + PSResourceGetVersion = '1.1.1' # PowerShellGet compatibility module only works when using PSResourceGet or ModuleFast. - UsePowerShellGetCompatibilityModule = $true + UsePowerShellGetCompatibilityModule = $false UsePowerShellGetCompatibilityModuleVersion = '3.0.23-beta23' } diff --git a/build.yaml b/build.yaml index 51d2844..ba13224 100644 --- a/build.yaml +++ b/build.yaml @@ -146,7 +146,7 @@ BuildWorkflow: publish: - Publish_Release_To_GitHub # Runs first, if token is expired it will fail early - # - publish_module_to_gallery + - publish_module_to_gallery ModuleBuildTasks: Sampler: diff --git a/src/Classes/002 LogCategory-list.ps1.old b/src/Classes/002 LogCategory-list.ps1.old new file mode 100644 index 0000000..c3865e1 --- /dev/null +++ b/src/Classes/002 LogCategory-list.ps1.old @@ -0,0 +1,90 @@ +#dotsource the corresponding object class +class ListOfLogCategory { + # Static property to hold the list of resources + static [System.Collections.Generic.List[LogCategoryObj]] $Resources + + # Static method to initialize the list of resources. Called in the other + # static methods to avoid needing to explicit initialize the value. + static [void] Initialize() { [ListOfLogCategory]::Initialize($false) } + + # Initialize the list of resources. + static [bool] Initialize([bool]$force) { + if ([ListOfLogCategory]::Resources.Count -gt 0 -and -not $force) { + return $false + } + [ListOfLogCategory]::Resources = [System.Collections.Generic.List[LogCategoryObj]]::new() + return $true + } + + # Ensure the LogCategoryObj is valid for the list. + static [void] Validate([LogCategoryObj]$Resource) { + $Prefix = 'Resource validation failed: Resource must be defined with the ContainerId, ResourceTypeName, and SourceType properties, but' + if ($null -eq $Resource) { throw "$Prefix was null" } + if ([string]::IsNullOrEmpty($Resource.ContainerId)) { + throw "$Prefix ContainerId wasn't defined" + } + if ([string]::IsNullOrEmpty($Resource.ResourceTypeName)) { + throw "$Prefix ResourceTypeName wasn't defined" + } + if ([string]::IsNullOrEmpty($Resource.SourceType)) { + throw "$Prefix SourceType wasn't defined" + } + } + + # Static methods to manage the list of LogCategoryObj. + # Add a LogCategoryObj if it's not already in the list. + static [void] Add([LogCategoryObj]$Resource) { + [ListOfLogCategory]::Initialize() + [ListOfLogCategory]::Validate($Resource) + $FindPredicate = { + param([LogCategoryObj]$r) + $r.ContainerId -eq $Resource.ContainerId -and + $r.ResourceTypeName -eq $Resource.ResourceTypeName -and + $r.SourceType -eq $Resource.SourceType + }.GetNewClosure() + if ([ListOfLogCategory]::Resources.Find($FindPredicate)) { + throw "Resource with ContainerId '$Resource.ContainerId', ResourceTypeName '$Resource.ResourceTypeName', and SourceType '$Resource.SourceType' already in list" + } + [ListOfLogCategory]::Resources.Add($Resource) + } + + # Clear the list of LogCategoryObj. + static [void] Clear() { + [ListOfLogCategory]::Initialize() + [ListOfLogCategory]::Resources.Clear() + } + + # Method to find the first LogCategoryObj that matches the given criteria. + # This method stops searching as soon as it finds a match, so it's more efficient for large lists. + # However, it will not alert you to duplicate entries. + static [LogCategoryObj] Find([scriptblock]$Predicate) { + [ListOfLogCategory]::Initialize() + return [ListOfLogCategory]::Resources.Find($Predicate) + } + + # Method to find all LogCategoryObjs that match the given criteria. + # This method searches the entire list and returns all matches. + # Use this method when you need to find all matches, or when you need to check for duplicates. + static [LogCategoryObj[]] FindAll([scriptblock]$Predicate) { + [ListOfLogCategory]::Initialize() + return [ListOfLogCategory]::Resources.FindAll($Predicate) + } + + # Remove a LogCategoryObj from the list. + static [void] Remove([LogCategoryObj]$Resource) { + [ListOfLogCategory]::Initialize() + [ListOfLogCategory]::Resources.Remove($Resource) + } + + # Remove a LogCategoryObj from the list by property and value. + static [void] RemoveBy([string]$Property, [string]$Value) { + [ListOfLogCategory]::Initialize() + $Index = [ListOfLogCategory]::Resources.FindIndex({ + param($r) + $r.$Property -eq $Value + }.GetNewClosure()) + if ($Index -ge 0) { + [ListOfLogCategory]::Resources.RemoveAt($Index) + } + } +} diff --git a/src/Classes/LogCategoryObj.ps1 b/src/Classes/LogCategoryObj.ps1 new file mode 100644 index 0000000..52cd119 --- /dev/null +++ b/src/Classes/LogCategoryObj.ps1 @@ -0,0 +1,118 @@ +class LogCategoryObj { + # Class properties + [string] $SourceType = "" + [string] $ContainerId = "" + [string] $ResourceTypeName = "" + [array] $LogCategory = @() + [array] $MetricCategory = @() + + + # ------------------------------ + # Input Validation + # ------------------------------ + + # Static method to validate the input parameters + static [void] Validate([string]$ContainerId, [string]$ResourceTypeName, [string]$SourceType) { + $errors = @() + # Basic validation for null or empty strings + if ([string]::IsNullOrEmpty($SourceType)) { + $errors += "SourceType cannot be null or empty" + } + if ([string]::IsNullOrEmpty($ContainerId)) { + $errors += "ContainerId cannot be null or empty" + } + if ([string]::IsNullOrEmpty($ResourceTypeName)) { + $errors += "ResourceTypeName cannot be null or empty" + } + + # Additional validation for SourceType 'Az' + # to ensure that ContainerId is a valid GUID + if ($SourceType -eq 'Az') { + $guid = New-Object Guid + if (-not [Guid]::TryParse($ContainerId, [ref]$guid)) { + $errors += "ContainerId must be a valid GUID when SourceType is 'Az'" + } + } + + # Throw an exception if there are any errors + if ($errors.Count -gt 0) { + throw ($errors -join "`n") + } + } + + # ------------------------------ + # Constructors + # ------------------------------ + + # Default constructor + LogCategoryObj() { + # There's no need to reinitialize LogCategory and MetricCategory in this constructor + # as they are already initialized when the class properties are declared. + # Typically, a constructor is used for any logic that needs to be executed during object creation. + # However, for these properties, such logic is not applicable. + } + + # Convenience constructor from hashtable + LogCategoryObj([hashtable]$Properties) { + [LogCategoryObj]::Validate($Properties.ContainerId, $Properties.ResourceTypeName, $Properties.SourceType) + $this.Init($Properties) + $this.GetDiagnosticSettings() + } + + # Common constructor for separate properties + LogCategoryObj([string]$ContainerId, [string]$ResourceTypeName, [string]$SourceType) { + [LogCategoryObj]::Validate($ContainerId, $ResourceTypeName, $SourceType) + $this.ContainerId = $ContainerId + $this.ResourceTypeName = $ResourceTypeName + $this.SourceType = $SourceType + $this.GetDiagnosticSettings() + } + + # ------------------------------ + # Methods + # ------------------------------ + + # Hashtable parser that initializes matching object properties + [void] Init([hashtable]$Properties) { + foreach ($key in $Properties.Keys) { + if ($this.psobject.properties.Match($key).Count -gt 0) { + $this.$key = $Properties[$key] + } + } + } + + # Method to get diagnostic settings + [void] GetDiagnosticSettings() { + $resource = Get-AzResource -ResourceType $this.ResourceTypeName | Select-Object -First 1 + + $diagnosticSettings = $null + + try { + $diagnosticSettings = (Get-AzDiagnosticSettingCategory -ResourceId $resource.ResourceId -ErrorAction SilentlyContinue) | Select-Object Name, CategoryType + } + catch { + $diagnosticSettings = $null + } + + if ($diagnosticSettings) { + + $diagnosticSettings | ForEach-Object { + if ($_.CategoryType -eq 'Logs') { + $this.LogCategory += $_.Name + } + elseif ($_.CategoryType -eq 'Metrics') { + $this.MetricCategory += $_.Name + } + } + } + else { + $this.LogCategory = @() + $this.MetricCategory = @() + } + } + + # Method to return a string representation of the resource + [string] ToString() { + return "Resource Type: $($this.ResourceTypeName) in ContainerId: $($this.ContainerId) from Source: $($this.SourceType)" + } +} \ No newline at end of file diff --git a/src/Private/Get-ProviderApiVersions.ps1 b/src/Private/Get-ProviderApiVersions.ps1 new file mode 100644 index 0000000..f68d1f7 --- /dev/null +++ b/src/Private/Get-ProviderApiVersions.ps1 @@ -0,0 +1,55 @@ +function Get-ProviderApiVersions { + <# +.SYNOPSIS + Gets the latest GA and Preview API versions per Azure resource type. + +.DESCRIPTION + This function queries Azure Resource Providers and builds a cache keyed by the + canonical resource type (for example Microsoft.Web/sites). + + For each resource type, it returns: + - LatestGAApiVersion: the latest stable API version (format YYYY-MM-DD) + - LatestPreviewApiVersion: the latest preview API version (suffix -preview) + + Versions that are neither GA nor preview (for example -alpha) are ignored. + +.EXAMPLE + Get-ProviderApiVersions + + Returns a cache for all discovered resource types. + +.EXAMPLE + $cache = Get-ProviderApiVersions -Verbose + $cache['Microsoft.ManagedIdentity/Identities'] + + Returns the latest GA/Preview versions for a given resource type. + +.OUTPUTS + System.Collections.Hashtable + Keys are strings "/". + Values are PSCustomObject with LatestGAApiVersion and LatestPreviewApiVersion. + #> + [CmdletBinding()] + param() + + $providerCache = @{} + + Write-Verbose "Retrieving Azure Resource Providers..." + $providers = Get-AzResourceProvider -ListAvailable + + foreach ($provider in $providers) { + foreach ($resourceType in $provider.ResourceTypes) { + $fullTypeName = "$($provider.ProviderNamespace)/$($resourceType.ResourceTypeName)" + + $gaVersions = $resourceType.ApiVersions | Where-Object { $_ -match '^\d{4}-\d{2}-\d{2}$' } + $previewVersions = $resourceType.ApiVersions | Where-Object { $_ -match '-preview$' } + + $providerCache[$fullTypeName] = [PSCustomObject]@{ + LatestGAApiVersion = ($gaVersions | Sort-Object -Descending | Select-Object -First 1) + LatestPreviewApiVersion = ($previewVersions | Sort-Object -Descending | Select-Object -First 1) + } + } + } + + return $providerCache +} \ No newline at end of file diff --git a/src/Private/Set-AzApiCallContext.ps1 b/src/Private/Set-AzApiCallContext.ps1 index 8f4f03c..f3c15d7 100644 --- a/src/Private/Set-AzApiCallContext.ps1 +++ b/src/Private/Set-AzApiCallContext.ps1 @@ -1,36 +1,50 @@ function Set-AzApiCallContext { <# .SYNOPSIS -Sets the context for Azure API calls. + Initializes AzAPICall context and a bearer token. .DESCRIPTION -The Set-AzApiCallContext function sets up the context for Azure API calls. It takes in parameters like SubscriptionId, TenantId, targetEndpoint, and others. It then initializes the AzAPICall and creates a bearer token for the specified target endpoint. + This function creates an AzAPICall configuration (initAzAPICall) for a given + subscription and tenant, then generates a bearer token for a target endpoint + (ARM, MicrosoftGraph, etc.). It supports -WhatIf/-Confirm via ShouldProcess. .PARAMETER SubscriptionId -The subscription ID for the Azure account. + Azure subscription id. + Does not accept pipeline input. .PARAMETER TenantId -The tenant ID for the Azure account. + Azure tenant id. + Does not accept pipeline input. .PARAMETER targetEndpoint -The target endpoint for the Azure API call. It must be one of 'MicrosoftGraph', 'ARM', 'KeyVault', 'LogAnalytics', 'MonitorIngest' and must match the specified pattern. + Target endpoint for which to create a token. + Valid values: MicrosoftGraph, ARM, KeyVault, LogAnalytics, MonitorIngest. + Does not accept pipeline input. .PARAMETER DebugAzAPICall -A boolean value indicating whether to debug the Azure API call. + Enables AzAPICall debug mode. + Default: False. .PARAMETER WriteMethod -The method to write the output. + Output method used by AzAPICall (for example Output). + Default: Output. .PARAMETER DebugWriteMethod -The method to write the debug output. + Debug output method used by AzAPICall (for example Warning). + Default: Warning. .PARAMETER SkipAzContextSubscriptionValidation -A boolean value indicating whether to skip Azure context subscription validation. + When True, skips AzContext subscription validation. + Default: True. .EXAMPLE -Set-AzApiCallContext -SubscriptionId "sub-id" -TenantId "tenant-id" -targetEndpoint "https://example.blob.core.windows.net" + Set-AzApiCallContext -SubscriptionId '' -TenantId '' -targetEndpoint ARM -WhatIf -This example shows how to set the Azure API call context. + Shows what would happen without executing. + +.OUTPUTS + None + Initializes AzAPICall and writes informational messages. #> [CmdletBinding(SupportsShouldProcess = $true)] @@ -43,7 +57,6 @@ This example shows how to set the Azure API call context. [Parameter(Mandatory = $true)] [ValidateSet('MicrosoftGraph', 'ARM', 'KeyVault', 'LogAnalytics', 'MonitorIngest')] - [ValidatePattern('^https://[a-z0-9]+\.blob\.core\.windows\.net$|^https://[a-z0-9]+\.blob\.storage\.azure\.net$')] [string]$targetEndpoint, [bool]$DebugAzAPICall = $False, diff --git a/src/Private/Set-ScriptExecutionPreference.ps1 b/src/Private/Set-ScriptExecutionPreference.ps1 index 3ef50f5..c1f600c 100644 --- a/src/Private/Set-ScriptExecutionPreference.ps1 +++ b/src/Private/Set-ScriptExecutionPreference.ps1 @@ -1,28 +1,38 @@ function Set-ScriptExecutionPreference { <# .SYNOPSIS - Sets the script execution preference. + Sets module Verbose/Debug preferences. .DESCRIPTION - The Set-ScriptExecutionPreference function sets the script execution preference. It supports three levels of verbosity: "Information", "Verbose", and "Debug". + This function configures $script:VerbosePreference and $script:DebugPreference + to control Verbose, Debug, and Data output across the module. .PARAMETER ExecutionPreference - The execution preference to be set. It can be one of the following: "Information", "Verbose", "Debug". Default is "Information". + Desired verbosity level. + Valid values: Information, Verbose, Debug. + Default: Information. + Does not accept pipeline input. .EXAMPLE - Set-ScriptExecutionPreference -ExecutionPreference "Verbose" - Sets the script execution preference to "Verbose". This will enable verbose logging. + Set-ScriptExecutionPreference + + Resets to Information mode (no Verbose/Debug output). .EXAMPLE - Set-ScriptExecutionPreference -ExecutionPreference "Debug" - Sets the script execution preference to "Debug". This will enable verbose and debug logging. + Set-ScriptExecutionPreference -ExecutionPreference Verbose + + Enables Verbose output and disables Debug. .EXAMPLE - Set-ScriptExecutionPreference - Sets the script execution preference to the default "Information". This will disable verbose and debug logging. + Set-ScriptExecutionPreference -ExecutionPreference Debug + + Enables Verbose and Debug output. + +.OUTPUTS + None .NOTES - The function changes the script scope variables $script:VerbosePreference and $script:DebugPreference. + Modifies script-scope variables; does not use ShouldProcess. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification = "This function only changes script scope variables")] [CmdletBinding()] diff --git a/src/Public/Get-ARGResourceType.ps1 b/src/Public/Get-ARGResourceType.ps1 new file mode 100644 index 0000000..3b8cc33 --- /dev/null +++ b/src/Public/Get-ARGResourceType.ps1 @@ -0,0 +1,81 @@ +function Get-ARGResourceType { + <# +.SYNOPSIS + Returns a profile per Azure resource type. + +.DESCRIPTION + This function queries Azure Resource Graph to retrieve the deployed resource + count per resource type (for example Microsoft.Web/sites), then enriches the + result with API versions published by Azure Resource Providers. + +.PARAMETER SubscriptionId + Subscription id(s) to query. + If not specified, the query runs at tenant scope. + Does not accept pipeline input. + +.EXAMPLE + Get-ARGResourceType + + Returns the tenant resource types with counts and the latest GA/Preview API versions. + +.EXAMPLE + Get-ARGResourceType -SubscriptionId '00000000-0000-0000-0000-000000000000' + + Queries a specific subscription (or multiple subscriptions). + +.OUTPUTS + PSCustomObject + Objects with ResourceType, ResourceCount, LatestGAApiVersion, LatestPreviewApiVersion, + and SupportsPreviewApi. + #> + [CmdletBinding()] + param ( + [string[]]$SubscriptionId + ) + + $query = @" +Resources +| summarize ResourceCount = count() by type +| order by type asc +"@ + + Write-Verbose "Running Azure Resource Graph query..." + if ($SubscriptionId) { + $argResults = Search-AzGraph -Query $query -Subscription $SubscriptionId + } + else { + $argResults = Search-AzGraph -Query $query -UseTenantScope + } + + Write-Verbose "Retrieving provider API versions..." + $providerInfo = Get-ProviderApiVersions + + if (-not $providerInfo) { + Write-Warning "Failed to retrieve provider information. Initializing an empty cache." + $providerInfo = @{} + } + + Write-Verbose "Enriching results..." + $results = foreach ($item in $argResults) { + $resourceType = $item.type + $apiInfo = $providerInfo[$resourceType] + + if (-not $apiInfo) { + Write-Warning "No provider information found for: $resourceType" + $apiInfo = [PSCustomObject]@{ + LatestGAApiVersion = $null + LatestPreviewApiVersion = $null + } + } + + [PSCustomObject]@{ + ResourceType = $resourceType + ResourceCount = $item.ResourceCount + LatestGAApiVersion = $apiInfo.LatestGAApiVersion + LatestPreviewApiVersion = $apiInfo.LatestPreviewApiVersion + SupportsPreviewApi = ($null -ne $apiInfo.LatestPreviewApiVersion) + } + } + + return $results +} \ No newline at end of file diff --git a/src/Public/Get-CsAzGovAssignment.ps1 b/src/Public/Get-CsAzGovAssignment.ps1 index 5a1d8dd..23fdbbc 100644 --- a/src/Public/Get-CsAzGovAssignment.ps1 +++ b/src/Public/Get-CsAzGovAssignment.ps1 @@ -1,32 +1,39 @@ function Get-CsAzGovAssignment { <# .SYNOPSIS - Retrieves Azure Governance Assignments. + Retrieves Azure governance assignments via Resource Graph. .DESCRIPTION - This function retrieves the list of security assessments from Azure and stores the governance assignments. + This function runs an Azure Resource Graph query (through the ARM Resource Graph API) + to retrieve governance assignments related to Microsoft Defender for Cloud assessments. + It can optionally filter to overdue items only. .PARAMETER azAPICallConf - A hashtable containing the configuration for the Azure API call. + AzAPICall configuration hashtable. + Does not accept pipeline input. .PARAMETER CsEnvironment - The environment for which the function is being run. + CyberShell environment name to inject in the output (csEnvironment field). + Does not accept pipeline input. .PARAMETER OverdueOnly - A switch parameter that retrieves only the overdue governance assignments. + When specified, filters assignments to the "Overdue" status only. + Does not accept pipeline input. .EXAMPLE - Get-CsAzGovAssignment -SubId "your-subscription-id" -azAPICallConf $yourConfig -CsEnvironment "your-environment" + $azAPICallConf = initAzAPICall -SubscriptionId4AzContext '' -TenantId4AzContext '' + Get-CsAzGovAssignment -azAPICallConf $azAPICallConf -CsEnvironment 'Azure' -.INPUTS - String, Hashtable, String + Retrieves all assignments (OnTime, Overdue, Unassigned, Completed). -.OUTPUTS - ArrayList - Returns an ArrayList of governance assignments. +.EXAMPLE + Get-CsAzGovAssignment -azAPICallConf $azAPICallConf -CsEnvironment 'Azure' -OverdueOnly -.NOTES - This function makes use of the AzAPICall function to make the API call to Azure. + Retrieves overdue assignments only. + +.OUTPUTS + System.Object + Returns the object emitted by AzAPICall (Resource Graph response content). #> param ( diff --git a/src/Public/Get-CsLogCategory.ps1 b/src/Public/Get-CsLogCategory.ps1 new file mode 100644 index 0000000..bc636f0 --- /dev/null +++ b/src/Public/Get-CsLogCategory.ps1 @@ -0,0 +1,84 @@ +function Get-CsLogCategory { + <# +.SYNOPSIS + Returns log/metric categories per resource type. + +.DESCRIPTION + This function builds a list of resource types present in the current subscription + using Get-AzResource, then materializes LogCategoryObj instances. It can optionally + filter to resource types that expose log categories only or metric categories only. + +.PARAMETER LogOnly + When specified, returns only resource types with at least one log category. + Cannot be used with MetricOnly. + +.PARAMETER MetricOnly + When specified, returns only resource types with at least one metric category. + Cannot be used with LogOnly. + +.EXAMPLE + Get-CsLogCategory + + Displays resource types and their available log/metric categories. + +.EXAMPLE + Get-CsLogCategory -LogOnly + + Displays only resource types that have log categories. + +.EXAMPLE + Get-CsLogCategory -MetricOnly + + Displays only resource types that have metric categories. + +.OUTPUTS + System.Object + Emits formatting data (Format-Table) intended for console display. + +.NOTES + Make sure you are connected to Azure (Connect-AzAccount) and the correct subscription + is selected (Set-AzContext) before running this function. + #> + + param ( + [Parameter(Mandatory = $false)] + [switch]$LogOnly, + + [Parameter(Mandatory = $false)] + [switch]$MetricOnly + ) + + if ($LogOnly -and $MetricOnly) { + throw "The LogOnly and MetricOnly parameters cannot be used at the same time." + } + + [ListOfLogCategory]::Clear() + + $resources = Get-AzResource | Select-Object SubscriptionId, ResourceType -Unique + $total = $resources.Count + $current = 0 + + $resources | ForEach-Object { + $current++ + Write-Progress -Activity "Processing resources" -Status "Resource $current of $total $($_.ResourceTypeName)" -PercentComplete ($current / $total * 100) + + $Resource = [LogCategoryObj]::new(@{ + ContainerId = $_.SubscriptionId + ResourceTypeName = $_.ResourceType + SourceType = 'Az' + }) + [ListOfLogCategory]::Add($Resource) + } + + if ($LogOnly) { + $ListOfLogCategory = [ListOfLogCategory]::FindAll({ param($r) $r.LogCategory.Length -gt 0 }) + } + elseif ($MetricOnly) { + $ListOfLogCategory = [ListOfLogCategory]::FindAll({ param($r) $r.MetricCategory.Length -gt 0 }) + } + else { + $ListOfLogCategory = [ListOfLogCategory]::LogCategoryObj + } + + Write-Output $ListOfLogCategory | Format-Table -AutoSize +} \ No newline at end of file diff --git a/src/Public/Import-CsEnvironment.ps1 b/src/Public/Import-CsEnvironment.ps1 index d69e032..2fd2839 100644 --- a/src/Public/Import-CsEnvironment.ps1 +++ b/src/Public/Import-CsEnvironment.ps1 @@ -1,26 +1,36 @@ function Import-CsEnvironment { <# .SYNOPSIS - Imports CyberShell environments and configuration from a JSONC file. + Imports CyberShell configuration from a JSONC file. .DESCRIPTION - The Import-CsEnvironment function imports CyberShell environments and configuration from a specified JSONC file. - If no file is specified, it defaults to the CyberShell-Config.jsonc file in the .cybershell directory of the user's home directory. + This function loads a JSONC configuration file (practically JSON) and exposes + CyberShell environments and settings. If no path is provided, it defaults to + $HOME/.cybershell/CyberShell-Config.jsonc (or the CYBERSHELL_CONFIG environment + variable when set). .PARAMETER JsonPath - The path to the JSONC file to import. If not specified, defaults to $HOME/.cybershell/CyberShell-Config.jsonc. + Path to the JSONC file to import. + Default: $HOME/.cybershell/CyberShell-Config.jsonc. + Does not accept pipeline input. .EXAMPLE - Import-CsEnvironment -JsonPath "C:\path\to\your\file.jsonc" + Import-CsEnvironment -.INPUTS - String. Path to the JSONC file. + Imports configuration from the default location. + +.EXAMPLE + Import-CsEnvironment -JsonPath "$HOME/.cybershell/CyberShell-Config.jsonc" + + Imports configuration from an explicit path. .OUTPUTS - Hashtable. The imported CyberShell data. + None + This function stores imported data in scope variables (CsData) rather than returning it. .NOTES - The imported data is stored in a global variable $global:CsData. + Imported data is stored in $script:CsData (module scope) and also copied to + $global:CsData for compatibility. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "", Justification = 'Global variable used for $CsData.')] [CmdletBinding()] @@ -69,21 +79,24 @@ function Import-CsEnvironment { } # Structured global data storage - $global:CsData = @{ + $script:CsData = @{ "Environments" = $rawData["CyberShellEnvironments"]; "Settings" = $rawData["Settings"]; } + # Backward compatibility + $global:CsData = $script:CsData + # Debug output for imported data Write-OutputPadded "Environment loaded:" -IdentLevel 1 -Type "Debug" -BlankLineBefore - Write-OutputPadded "Environment: $($global:CsData.Environments | ConvertTo-Json)" -IdentLevel 1 -Type "Data" + Write-OutputPadded "Environment: $($script:CsData.Environments | ConvertTo-Json)" -IdentLevel 1 -Type "Data" # debug output for settings # if settings exist, output them - if ($global:CsData.Settings) { + if ($script:CsData.Settings) { Write-OutputPadded "Settings loaded:" -IdentLevel 1 -Type "Debug" -BlankLineBefore - Write-OutputPadded "Settings: $($global:CsData.Settings | ConvertTo-Json) " -IdentLevel 1 -Type "Data" + Write-OutputPadded "Settings: $($script:CsData.Settings | ConvertTo-Json) " -IdentLevel 1 -Type "Data" } else { Write-OutputPadded "No settings found in the config file." -IdentLevel 1 -Type "Debug" @@ -91,14 +104,13 @@ function Import-CsEnvironment { # CSData.settings.ExecutionPreference exist then set the script execution preference - if ($global:CsData.Settings.ExecutionPreference) { - Set-ScriptExecutionPreference -ExecutionPreference $global:CsData.Settings.ExecutionPreference - Write-OutputPadded "Script execution preference set to $($global:CsData.Settings.ExecutionPreference)" -IdentLevel 1 -Type "Debug" + if ($script:CsData.Settings.ExecutionPreference) { + Set-ScriptExecutionPreference -ExecutionPreference $script:CsData.Settings.ExecutionPreference + Write-OutputPadded "Script execution preference set to $($script:CsData.Settings.ExecutionPreference)" -IdentLevel 1 -Type "Debug" Write-OutputPadded " " -Type "Debug" } - - If ($global:CsData.Settings.ExecutionPreference -eq "Debug") { + If ($script:CsData.Settings.ExecutionPreference -eq "Debug") { Write-OutputPadded "Debug Informations:" -IdentLevel 1 -Type "Debug" } else { @@ -108,6 +120,6 @@ function Import-CsEnvironment { Write-OutputPadded "JsonPath: $JsonPath" -IdentLevel 2 -Type "Debug" Write-OutputPadded "Imported CyberShell Data:" -IdentLevel 2 -Type "Debug" -BlankLineBefore - Write-OutputPadded "$(ConvertTo-Json $global:CsData -Depth 20)" -IdentLevel 2 -Type "Data" + Write-OutputPadded "$(ConvertTo-Json $script:CsData -Depth 20)" -IdentLevel 2 -Type "Data" } diff --git a/src/Public/Invoke-CsFunction.ps1 b/src/Public/Invoke-CsFunction.ps1 index ddf5949..4ce5808 100644 --- a/src/Public/Invoke-CsFunction.ps1 +++ b/src/Public/Invoke-CsFunction.ps1 @@ -1,36 +1,46 @@ function Invoke-CsFunction { <# .SYNOPSIS - Invokes a CyberShell function across multiple environments. + Invokes a CyberShell function across one or more environments. .DESCRIPTION - The Invoke-CsFunction function invokes a specified CyberShell function across multiple environments. - The function name, parameters, and target environments can be specified. + This function invokes a generic CyberShell command (for example Get-CsSomething) + across environments declared in the CyberShell configuration. + + The "-Cs" prefix is replaced with an environment-specific prefix (for example + Az for AzureCloud). The configuration must be loaded first using Import-CsEnvironment. .PARAMETER CsFunctionName - The name of the CyberShell function to invoke. This should include the '-Cs' prefix. + Name of the CyberShell function to invoke, including the "-Cs" prefix + (for example Get-CsAzGovAssignment). + Does not accept pipeline input. .PARAMETER CsFunctionParams - A hashtable of parameters to pass to the CyberShell function. + Hashtable of parameters to pass to the target cmdlet. + Does not accept pipeline input. .PARAMETER CsEnvironmentName - The name of a specific environment to target. If not specified, the function is invoked across all environments. + Name of a specific environment to target. If omitted, targets all environments. + Does not accept pipeline input. .PARAMETER CsEnvironmentType - The type of environment to target. If not specified, the function is invoked across all types of environments. + Type of environment to target (for example AzureCloud, AWS, GCP). If omitted, + targets all types. + Does not accept pipeline input. .EXAMPLE - $params = @{ "ResourceGroupName" = "MyResourceGroup"; "Name" = "MyVM" } - Invoke-CsFunction -CsFunctionName "Get-CsVM" -CsFunctionParams $params -CsEnvironmentName "Prod" + Import-CsEnvironment + $params = @{ azAPICallConf = $azAPICallConf; CsEnvironment = 'Azure' } + Invoke-CsFunction -CsFunctionName 'Get-CsAzGovAssignment' -CsFunctionParams $params -CsEnvironmentType 'AzureCloud' -.INPUTS - String, Hashtable, String, String + Invokes the command for all AzureCloud environments. .OUTPUTS - Varies based on the CyberShell function invoked. + System.Object + Output depends on the invoked function. .NOTES - The CyberShell data must be loaded (using Import-CsEnvironment) before this function can be used. + Requires Import-CsEnvironment to have been executed (initializes $script:CsData). #> [CmdletBinding()] param ( diff --git a/src/Public/Set-CsConfig.ps1 b/src/Public/Set-CsConfig.ps1 index 5bb0e6e..c7831ee 100644 --- a/src/Public/Set-CsConfig.ps1 +++ b/src/Public/Set-CsConfig.ps1 @@ -1,33 +1,45 @@ Function Set-CsConfig { <# .SYNOPSIS - Creates or updates a CyberShell configuration file. + Creates or updates the CyberShell configuration file. .DESCRIPTION - The Set-CsConfig function creates a new CyberShell configuration file in the .CyberShell directory in the user's home directory. If the file already exists, the function's behavior depends on the parameters provided: - - If the -Overwrite switch is provided, the function will create a backup of the existing file and then overwrite it. - - If the -Edit switch is provided, the function will open the existing file in Visual Studio Code. - - If neither switch is provided and the file exists, the function will throw an error. + This function creates a JSONC configuration file under $HOME/.cybershell. + If the file already exists, you can either overwrite it (with a backup) using + -Overwrite, or open it using -Edit. If the file exists and neither option is + provided, the function returns an error. + + This function supports -WhatIf/-Confirm (SupportsShouldProcess). .PARAMETER Edit - If this switch is provided, the function will open the configuration file in Visual Studio Code after creating it. If the file already exists, the function will open the existing file instead of throwing an error. + When specified, opens the configuration file in Visual Studio Code. + If the file does not exist, it is created before opening. + Does not accept pipeline input. .PARAMETER Overwrite - If this switch is provided, the function will overwrite the existing configuration file after creating a backup. The backup file will be named "CyberShell-Config.bak.jsonc", or "CyberShell-Config.bakX.jsonc" if a backup file already exists, where X is a number. + When specified, creates a backup and overwrites the configuration file if it exists. + Does not accept pipeline input. + +.EXAMPLE + Set-CsConfig + + Creates the default configuration file if missing. .EXAMPLE - Set-CsConfig -Edit -Overwrite + Set-CsConfig -Edit + + Creates (if needed) and opens the file in VS Code. - This command creates a new CyberShell configuration file in the .CyberShell directory in the user's home directory, overwrites the existing file if it exists (after creating a backup), and opens the new or existing file in Visual Studio Code. +.EXAMPLE + Set-CsConfig -Overwrite -Edit -.INPUTS - None. You cannot pipe objects to Set-CsConfig. + Backs up the existing file, recreates it, then opens it. .OUTPUTS - None. This function does not return any output. + None .NOTES - The configuration file is a JSONC file, which is a JSON file that supports comments. The default configuration file created by this function includes comments to explain each setting. + The generated file is JSONC (JSON with comments) written via Set-Content. #> [CmdletBinding(SupportsShouldProcess = $true)] diff --git a/src/Public/Update-CsAzGovAssignment.ps1 b/src/Public/Update-CsAzGovAssignment.ps1 index dfc20f8..b860d98 100644 --- a/src/Public/Update-CsAzGovAssignment.ps1 +++ b/src/Public/Update-CsAzGovAssignment.ps1 @@ -1,46 +1,68 @@ function Update-CsAzGovAssignment { <# .SYNOPSIS -Updates an Azure Governance Assignment. + Updates an Azure governance assignment. .DESCRIPTION -The Update-CsAzGovAssignment function updates an Azure Governance Assignment with the provided parameters. + This function updates a governance assignment associated with a Microsoft Defender + for Cloud assessment. It retrieves the existing assignment, applies the requested + changes (due date, owner, notifications, etc.), then submits the update via the ARM API. + The function supports -WhatIf/-Confirm. .PARAMETER azAPICallConf -A hashtable containing the configuration for the Azure API call. + AzAPICall configuration hashtable. + Does not accept pipeline input. .PARAMETER resourceId -The unique identifier of the resource. + ARM resource id where the assessment is attached. + Accepts pipeline input by property name. .PARAMETER AssessmentName -The name of the assessment. + Assessment name (resource name segment). + Accepts pipeline input by property name. .PARAMETER assignmentKey -The key of the assignment. + Governance assignment key. + Accepts pipeline input by property name. .PARAMETER RemediationDueDate -The due date for remediation. This is optional. + Remediation due date. Optional. + Does not accept pipeline input. .PARAMETER IsGracePeriod -Indicates whether there is a grace period. This is optional. + Indicates whether a grace period is enabled. Optional. + Does not accept pipeline input. .PARAMETER OwnerEmailAddress -The email address of the owner. This is optional. + Owner email address. Optional. + Does not accept pipeline input. .PARAMETER OwnerEmailNotification -Indicates whether the owner will receive email notifications. This is optional. + Enables/disables owner email notification. Optional. + Does not accept pipeline input. .PARAMETER ManagerEmailNotification -Indicates whether the manager will receive email notifications. This is optional. + Enables/disables manager email notification. Optional. + Does not accept pipeline input. .PARAMETER NotificationDayOfWeek -The day of the week when notifications will be sent. This is optional and must be one of 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'. + Day of week for notifications. Optional. + Valid values: Monday..Sunday. + Does not accept pipeline input. .EXAMPLE -$azAPICallConf = @{...} -Update-CsAzGovAssignment -azAPICallConf $azAPICallConf -resourceId "resourceId" -AssessmentName "AssessmentName" -assignmentKey "assignmentKey" + Update-CsAzGovAssignment -azAPICallConf $azAPICallConf -resourceId $resourceId -AssessmentName $AssessmentName -assignmentKey $assignmentKey -RemediationDueDate (Get-Date).AddDays(30) -This example updates an Azure Governance Assignment with the provided parameters. + Sets the remediation due date to 30 days from now. + +.EXAMPLE + Update-CsAzGovAssignment -azAPICallConf $azAPICallConf -resourceId $resourceId -AssessmentName $AssessmentName -assignmentKey $assignmentKey -OwnerEmailAddress 'owner@contoso.com' -NotificationDayOfWeek 'Monday' + + Updates the owner and the notification day. + +.OUTPUTS + System.Object + Returns the object emitted by AzAPICall for the PUT request. #> [CmdletBinding(SupportsShouldProcess)] param ( diff --git a/src/Public/Write-OutputPadded.ps1 b/src/Public/Write-OutputPadded.ps1 index 0a11bfb..9a851c6 100644 --- a/src/Public/Write-OutputPadded.ps1 +++ b/src/Public/Write-OutputPadded.ps1 @@ -1,54 +1,55 @@ function Write-OutputPadded { <# .SYNOPSIS - Writes colorized and formatted output to the console. + Writes colorized console output with indentation. .DESCRIPTION - The Write-OutputPadded function writes colorized and formatted output to the console. It supports indentation, centering, and different types of messages (Error, Warning, Success, Information, Data, Debug, Important). + This function formats text for console display with indentation, centering and + message styles (Information, Success, Warning, Error, Important, Verbose, Debug, Data). + Verbose/Debug/Data messages are controlled by $script:VerbosePreference and + $script:DebugPreference. .PARAMETER Text - The text to be written to the console. + Text to display. + Does not accept pipeline input. .PARAMETER IdentLevel - The level of indentation for the output text. Default is 0. + Indentation level (4 spaces per level). + Default: 0. .PARAMETER Width - The width of the output text. Default is 120. + Total output width used for centering and title borders. + Default: 120. .PARAMETER Centered - If set, the output text will be centered. + When specified, centers the text within Width. .PARAMETER isTitle - If set, the output text will be formatted as a title. + When specified, writes a top/bottom border and applies centering. .PARAMETER Type - The type of the message. It can be one of the following: "Error", "Warning", "Success", "Information", "Data", "Debug", "Important". The type determines the color of the output text. + Message type that controls coloring. + Valid values: Information, Success, Warning, Error, Important, Verbose, Debug, Data. .PARAMETER BlankLineBefore - If set, a blank line will be written before the output text. + When specified, writes a blank line before the output. .EXAMPLE - Write-OutputPadded -Text "Title" -isTitle -Type "Important" - Writes the text "Title" formatted as a title and colors it as an Important message. + Write-OutputPadded -Text 'Governance Assignments' -IdentLevel 1 -isTitle -Type Information -.EXAMPLE - Write-OutputPadded -Text "This is a ERROR demo text" -Type "Error" -IdentLevel 2 - Writes the text "This is a ERROR demo text" with an indentation level of 2 and colors it as an Error message. + Writes an indented title. .EXAMPLE - Write-OutputPadded -Text "This is a WARNING demo text" -Type "Warning" -IdentLevel 2 - Writes the text "This is a WARNING demo text" with an indentation level of 2 and colors it as a Warning message. + Write-OutputPadded -Text 'Something failed' -Type Error -IdentLevel 2 -.EXAMPLE - Write-OutputPadded -Text "This is a SUCCESS demo text" -Type "Success" -IdentLevel 2 - Writes the text "This is a SUCCESS demo text" with an indentation level of 2 and colors it as a Success message. + Writes an indented error message. -.EXAMPLE - Write-OutputPadded -Text "This is a INFORMATION demo text" -Type "Information" -IdentLevel 2 - Writes the text "This is a INFORMATION demo text" with an indentation level of 2 and colors it as an Information message. +.OUTPUTS + None + Writes to the host via Write-Host. .NOTES - The function uses Write-Host for colorized output formatting. + Uses Write-Host to support coloring. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification = "Write-Host used for colorized output formatting.")]