Modules/AzureDevOpsDsc.Common/Resources/Functions/Public/AzDoAreaNodes/Get-AzDoAreaNodes.ps1
|
<#
.SYNOPSIS Retrieves Azure DevOps area nodes for a specified project. .DESCRIPTION The Get-AzDoAreaNodes function retrieves Azure DevOps area nodes for a specified project. It formats the provided area paths, retrieves cached area nodes, and compares the current and desired area paths to determine the necessary actions based on the Ensure parameter. .PARAMETER ProjectName The name of the Azure DevOps project. This parameter is mandatory. .PARAMETER AreaPaths An optional array of strings specifying the area paths to be retrieved. .PARAMETER LookupResult An optional hashtable for lookup results. .PARAMETER Ensure An optional parameter to ensure the state of the area nodes. Possible values are 'Present' or 'Absent'. .PARAMETER Force A switch parameter to force the execution of the function. .EXAMPLE Get-AzDoAreaNodes -ProjectName "MyProject" -AreaPaths @("Area1", "Area2") Retrieves the area nodes for the specified project "MyProject" with the specified area paths "Area1" and "Area2". .EXAMPLE Get-AzDoAreaNodes -ProjectName "MyProject" -Ensure Absent Ensures that the area nodes for the specified project "MyProject" are absent. .NOTES This function uses cached area nodes to determine the current state and compares it with the desired state. It returns a result object containing the status, reason, and propertiesChanged based on the comparison. #> Function Get-AzDoAreaNodes { [CmdletBinding()] param ( # Mandatory parameter for the project name [Parameter(Mandatory = $true)] [string]$ProjectName, # Optional parameter for specifying area paths [Parameter(Mandatory = $false)] [System.String[]]$AreaPaths, # Optional hashtable for lookup results [Parameter()] [HashTable]$LookupResult, # Optional parameter to ensure state [Parameter()] [Ensure]$Ensure, # Switch parameter to force execution [Parameter()] [System.Management.Automation.SwitchParameter] $Force ) # Log the start of function execution with verbose output Write-Verbose "[Get-AzDoAreaNodes] Start function execution" Write-Verbose "[Get-AzDoAreaNodes] ProjectName: $ProjectName" Write-Verbose "[Get-AzDoAreaNodes] AreaPaths: $($AreaPaths | Out-String)" # Format the provided area paths for the specified project. Add missing classification node paths if ($AreaPaths.Count -ne 0) { $AreaPaths = $AreaPaths | Format-AzDoAreaPath -ProjectName $ProjectName | Get-AllAzDoClassificationNodePaths } Write-Verbose "[Get-AzDoAreaNodes] FormattedAreaPaths $($AreaPaths | Out-String)" # Retrieve the global organization name $OrganizationName = (Get-AzDoOrganizationName) Write-Verbose "[Get-AzDoAreaNodes] OrganizationName: $OrganizationName" # Initialize the result object with default values $getAreaResult = @{ Ensure = [Ensure]::Absent propertiesChanged = @{ toAdd = @() toRemove = @() } ProjectName = $ProjectName AreaPaths = $AreaPaths Status = [DSCGetSummaryState]::Unchanged Reason = $null CachedAreaNodes = $null } # Refresh this project's area nodes from the live API before reading them. # The init-time cache does not contain projects created later (so the top-level area node — # required to reclassify work items on removal — would be missing), and changes made in a # previous DSC runspace are not reliably reflected in this runspace's in-memory cache. Clearing # and repopulating from live makes the comparison authoritative and idempotent. $existingKeys = @((Get-CacheObject -CacheType 'LiveAreaNodes' | Where-Object { $_.Key -like "\$ProjectName\Area*" }).Key | Where-Object { $_ }) ForEach ($existingKey in $existingKeys) { Remove-CacheItem -Key $existingKey -Type 'LiveAreaNodes' } $liveAreaNodes = List-DevOpsClassificationNodes -ProjectName $ProjectName -OrganizationName $OrganizationName ForEach ($liveNode in $liveAreaNodes) { if ($liveNode.structureType -eq 'area') { Format-ClassificationNode -Node $liveNode -CacheType 'LiveAreaNodes' } } # Retrieve cached area nodes from cache $cachedAreaNodes = (Get-CacheObject -CacheType 'LiveAreaNodes' | Where-Object { $_.Key -like "\$ProjectName\Area*" }).Value Write-Verbose "[Get-AzDoAreaNodes] Retrieved cached area nodes: $($cachedAreaNodes | Out-String)" # Set the cached area nodes in the result object for use by other functions $getAreaResult.cachedAreaNodes = $cachedAreaNodes # We only need the path to perform the comparison. $cachedAreaNodesPath = $cachedAreaNodes.path Write-Verbose "[Get-AzDoAreaNodes] Cached area nodes paths: $($cachedAreaNodesPath | Out-String)" # Check if the cached area nodes contain a top-level node $isTopLevel = $cachedAreaNodesPath | Where-Object { $_ -eq "\$ProjectName\Area" } Write-Verbose "[Get-AzDoAreaNodes] Is top-level node present: $isTopLevel" # Handle case where no area paths are specified and only top-level node exists (or cache is empty for this project) if ($AreaPaths.Count -eq 0 -and ($null -eq $cachedAreaNodesPath -or $cachedAreaNodesPath.Count -eq 0 -or ($cachedAreaNodesPath.Count -eq 1 -and $isTopLevel))) { Write-Verbose "[Get-AzDoAreaNodes] AreaPaths is not specified and no Area Nodes exist in cache" $getAreaResult.status = [DSCGetSummaryState]::Unchanged $getAreaResult.reason = 'Area Node does not exist and only the top level Area Node exists' return $getAreaResult } # Handle case where no area paths are specified if ($AreaPaths.Count -eq 0) { Write-Verbose "[Get-AzDoAreaNodes] AreaPaths is not specified" $getAreaResult.status = [DSCGetSummaryState]::Missing $getAreaResult.reason = 'Area Node does not exist' if ($Ensure -eq [Ensure]::Absent) { $getAreaResult.status = [DSCGetSummaryState]::NotFound $getAreaResult.propertiesChanged.toAdd = $cachedAreaNodesPath | Where-Object { $_ -ne "\$ProjectName\Area" } | ForEach-Object { @{ Path = $_ } } } else { $getAreaResult.propertiesChanged.ToRemove = $cachedAreaNodesPath | Where-Object { $_ -ne "\$ProjectName\Area" } | ForEach-Object { @{ Path = $_ } } } return $getAreaResult } # Handle case where only top-level node exists (or cache is entirely empty for this project) if ($null -eq $cachedAreaNodesPath -or $cachedAreaNodesPath.Count -eq 0 -or ($cachedAreaNodesPath.Count -eq 1 -and $isTopLevel)) { Write-Verbose "[Get-AzDoAreaNodes] Only top-level node exists" $getAreaResult.status = [DSCGetSummaryState]::NotFound if ($Ensure -eq [Ensure]::Absent) { $getAreaResult.status = [DSCGetSummaryState]::Missing $getAreaResult.propertiesChanged.toRemove = $AreaPaths | Where-Object { $_ -ne "\$ProjectName\Area" } | ForEach-Object { @{ Path = $_ } } } else { $getAreaResult.propertiesChanged.toAdd = $AreaPaths | Where-Object { $_ -ne "\$ProjectName\Area" } | ForEach-Object { @{ Path = $_ } } } $getAreaResult.reason = 'Area Node does not exist' return $getAreaResult } # Extract paths from cached area nodes for comparison $differenceObject = $cachedAreaNodesPath Write-Verbose "[Get-AzDoAreaNodes] Difference object for comparison: $($differenceObject | Out-String)" # Compare current and desired area paths, excluding top-level areas $currentList = Compare-Object -ReferenceObject $AreaPaths -DifferenceObject $differenceObject -IncludeEqual | Where-Object { $_.InputObject -ne "\$ProjectName\Area" } Write-Verbose "[Get-AzDoAreaNodes] Comparison result: $($currentList | Out-String)" # Determine actions based on Ensure parameter if ($Ensure -eq [Ensure]::Absent) { # Identify items present in both lists for removal $toRemove = ($currentList | Where-Object { ($_.SideIndicator -eq '==') }).InputObject Write-Verbose "[Get-AzDoAreaNodes] Items to delete: $($toRemove | Out-String)" } else { # Identify items missing in current or desired state $toAdd = ($currentList | Where-Object { $_.SideIndicator -eq '<=' }).InputObject $toRemove = ($currentList | Where-Object { $_.SideIndicator -eq '=>' }).InputObject Write-Verbose "[Get-AzDoAreaNodes] Items to add: $($toAdd | Out-String)" Write-Verbose "[Get-AzDoAreaNodes] Items to delete: $($toRemove | Out-String)" } # Update status based on differences between current and desired states if (($toRemove.count -ne 0) -and ($toAdd.count -ne 0)) { $getAreaResult.status = [DSCGetSummaryState]::Changed Write-Verbose "[Get-AzDoAreaNodes] Status changed: Both additions and deletions detected" } elseif ($toRemove.count -ne 0) { $getAreaResult.status = [DSCGetSummaryState]::Missing Write-Verbose "[Get-AzDoAreaNodes] Status missing: Deletions detected" } elseif ($toAdd.count -ne 0) { $getAreaResult.status = [DSCGetSummaryState]::NotFound Write-Verbose "[Get-AzDoAreaNodes] Status not found: Additions detected" } # Update propertiesChanged with determined additions and deletions $getAreaResult.propertiesChanged = @{ toRemove = $toRemove | ForEach-Object { @{ Path = $_ } } toAdd = $toAdd | ForEach-Object { @{ Path = $_ } } } # Return the result object with all computed information Write-Verbose "[Get-AzDoAreaNodes] Final result: $($getAreaResult | Out-String)" return $getAreaResult } |