AzureDevOpsDscNative.psd1

@{
    RootModule           = 'AzureDevOpsDscNative.psm1'

    # Version number of this module.
    #
    # This value is a fallback for local and CI builds only. Released versions are
    # driven by the git tag: the publish workflow sets $env:ModuleVersion from the
    # tag, and the build stamps that value into the packaged manifest instead of this
    # one. Do not hand-edit this to cut a release - push a vX.Y.Z tag.
    moduleVersion      = '1.2.0'

    # ID used to uniquely identify this module
    GUID                 = 'e7eb078e-5e97-42be-ae8f-decb140fc38e'

    # Author of this module
    Author               = 'ZanattaMichael'

    # Company or vendor of this module
    CompanyName          = 'ZanattaMichael'

    # Copyright statement for this module
    Copyright            = 'Copyright the DSC Community contributors and ZanattaMichael. All rights reserved.'

    # Description of the functionality provided by this module
    Description          = 'A fork of the DSC Community AzureDevOpsDsc module with native DSC v3 support: every resource is discoverable and invokable by dsc.exe via the Microsoft.Adapter/PowerShell adapter, using generated adapted resource manifests, without requiring a wrapper resource.'

    # Minimum version of the Windows PowerShell engine required by this module
    PowerShellVersion  = '7.0'

    # Minimum version of the common language runtime (CLR) required by this module
    CLRVersion           = '4.0'

    # Functions to export from this module
    #FunctionsToExport = @()

    # Cmdlets to export from this module
    #CmdletsToExport = @()

    # Variables to export from this module
    VariablesToExport    = @()

    # Aliases to export from this module
    AliasesToExport      = @()

    # Import all the 'DSCClassResource', modules as part of this module
    NestedModules        = @()

    DscResourcesToExport = @('AzDoGroupPermission','AzDoOrganizationGroup','AzDoProject','AzDoProjectServices','AzDoProjectGroup','AzDoGroupMember','AzDoGitRepository','AzDoGitPermission','AzDoAreaPermission','AzDoIterationPermission','AzDoWIPTags','AzDoAreaNodes','AzDoIterationNodes','AzDoBranchPolicy','AzDoVariableGroup','AzDoServiceConnection','AzDoPipelineEnvironment','AzDoAgentPool','AzDoAgentQueue','AzDoTeam','AzDoTeamMember','AzDoPipeline','AzDoPipelinePermission','AzDoEnvironmentApproval','AzDoVariableGroupPermission','AzDoServiceConnectionPermission','AzDoAgentPoolPermission','AzDoSecurityNamespacePermission','AzDoArtifactFeed','AzDoArtifactFeedPermission','AzDoDeploymentGroup','AzDoTaskGroup','AzDoOrganizationSettings','AzDoExtension','AzDoAuditStream','AzDoProjectPermission','AzDoEnvironmentPermission','AzDoWiki','AzDoNotificationSubscription','AzDoRepositorySettings','AzDoCheckConfiguration','AzDoTeamSettings','AzDoArtifactFeedSettings','AzDoArtifactFeedView','AzDoProcess','AzDoProcessPermission','AzDoUserEntitlement','AzDoServiceHook','AzDoPipelineSettings','AzDoQueryFolder','AzDoWorkItemQuery','AzDoQueryPermission','AzDoWIPTagHygiene','AzDoSecureFile','AzDoSecureFilePermission','AzDoPipelineFolder','AzDoPipelineFolderPermission','AzDoGroupEntitlement','AzDoServicePrincipalEntitlement','AzDoPicklist','AzDoProcessWorkItemType','AzDoProcessField','AzDoProcessState','AzDoProcessRule','AzDoProcessBehavior','AzDoPipelineAuthorization','AzDoEnvironmentKubernetesResource','AzDoEnvironmentVMResource','AzDoDeploymentGroupTarget','AzDoReleaseFolder','AzDoReleaseFolderPermission','AzDoReleaseDefinitionPermission','AzDoTestVariable','AzDoTestConfiguration','AzDoTestPlan','AzDoTestSuite','AzDoBuildRetentionSettings','AzDoWikiPage','AzDoTaggingPermission','AzDoAnalyticsPermission','AzDoAnalyticsViewsPermission')

    RequiredAssemblies   = @()

    # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
    PrivateData          = @{

        PSData = @{
            # Set to a prerelease string value if the release should be a prerelease.
            Prerelease   = ''

            # Tags applied to this module. These help with module discovery in online galleries.
            Tags         = @('DesiredStateConfiguration', 'DSC', 'DSCResourceKit', 'DSCResource', 'DSCv3', 'AzureDevOps')

            # A URL to the license for this module.
            LicenseUri   = 'https://github.com/ZanattaMichael/AzureDevOpsDsc/blob/main/LICENSE'

            # A URL to the main website for this project.
            ProjectUri   = 'https://github.com/ZanattaMichael/AzureDevOpsDsc'

            # A URL to an icon representing this module.
            IconUri      = 'https://dsccommunity.org/images/DSC_Logo_300p.png'

            # ReleaseNotes of this module.
            # Per-release notes are generated from the CHANGELOG's [Unreleased] section
            # at build time and attached to the GitHub Release. This link is the fallback
            # shown on the Gallery listing.
            ReleaseNotes = '## [1.2.0] - 2026-10-04
 
### Changed
 
- Build
  - `azure-pipelines.yml` brought in line with the GitHub workflows so it runs
    on current Microsoft-hosted agents: full-history checkout for GitVersion,
    GitVersion.Tool 5.12.0 run with `DOTNET_ROLL_FORWARD=Major` (hosted images
    no longer ship .NET 6/7), unit tests run under Pester 5.7.1 as in
    `unit-tests.yml` instead of the Pester 4 `build.ps1 -tasks test` path,
    HQRM reported but non-gating, and the retired Codecov bash uploader and
    `PublishCodeCoverageResults@1` removed. The Deploy stage no longer requires a
    `dsccommunity` organization; it runs only on a `v*` tag with the pipeline
    variable `PublishRelease` set to `true`.
  - The integration workflows now cope with more than one runner carrying the
    `AZDO-AGENT` label. The v2 and v3 live-organization jobs share one
    concurrency group, so they no longer run against the organization at the same
    time and delete each other''s test objects in their pre-run teardown.
  - The integration workflows no longer move module copies out of, and back
    into, the runner''s standard module folders around each run - a failed move
    there stranded copies in `RUNNER_TEMP`, which the runner empties between
    jobs. They load the module only from their workspace build and now fail,
    naming each one, if a copy is installed in a standard module folder.
    `tests/Integration/README.md` lists what an `AZDO-AGENT` machine needs.
  - `scripts/redeploy-module.ps1` no longer copies the build into
    `Documents\PowerShell\Modules`. It puts `output\builtModule` at the front of
    `PSModulePath` for the current session and warns about any copy in a
    standard module folder.
- AzureDevOpsDscNative
  - `AzDoProject.ProcessTemplate` no longer restricts a project to the four
    system processes (`Agile`, `Scrum`, `CMMI`, `Basic`) via `ValidateSet` -
    any process name known to the organization, including an inherited
    process, is accepted and resolved against the `LiveProcesses` cache,
    falling back to a live lookup. `Set` can now change a project''s process: `Get-AzDoProject`
    compares the project''s current process against the desired one and, when
    they differ, resolves both to their system-process ancestor
    (`Get-AzDoProcessFamilyRootId`) to decide whether Azure DevOps will permit
    the migration. A compatible change (moving between a system process and
    one of its inherited children, or between two inherited children of the
    same parent) is applied via the new `projectprocessmigration` endpoint
    (`Move-DevOpsProjectProcess`); an incompatible one - crossing unrelated
    process families - is refused with a clear error in both `Get` and `Set`,
    since `Get`''s `Error` status still routes to `Set` and the refusal has to
    be repeated there to hold. Previously, `ProcessTemplate` was listed in
    `GetDscResourcePropertyNamesWithNoSetSupport()` and `Update-DevOpsProject`
    took a `ProcessTemplateId` parameter it never referenced, so a process
    change on an existing project was always a silent no-op ([issue #75](https://github.com/ZanattaMichael/AzureDevOpsDsc/issues/75)).
 
- Performance
  - `Set()` now runs the resource''s `Get` function once instead of twice:
    `SetToDesiredState()` reads the current state and passes it to a new
    `GetDscRequiredAction([Hashtable])` overload rather than reading it again.
  - The fixed 2-second sleep after every `Set()` is gone. `GetPostSetWaitTimeMs()`
    now defaults to `0`; a resource whose API needs time before a write is
    readable overrides it.
  - Caches are loaded on first use. `Construct()` no longer initializes every
    cache type (with `-Debug` forced on) each time a resource is instantiated;
    `Get-CacheObject` already imports a cache when it is first read. A missing
    cache file is now reported as verbose output rather than a warning, since
    it is the normal state for a cache nothing has written yet.
  - Cache lookups no longer copy or scan the whole cache:
    - `Get-CacheObject` returns the live cache list instead of letting
      PowerShell unroll it into a copy on every read. An empty cache comes back
      as an empty list rather than `$null`, and a cache stored as an array is
      normalized to a `List[CacheItem]` once.
    - `Add-CacheItem` and `Get-CacheItem` find a key through a new
      case-insensitive key index (`Get-CacheKeyIndex`) instead of a
      `Where-Object` scan, so filling a cache is linear rather than quadratic.
      The index is rebuilt whenever the cache list is replaced or its count
      changes, and a cache holding a duplicate key falls back to scanning.
    - `Add-CacheItem` replaces an existing key in place instead of copying the
      cache through `Remove-CacheItem`.
    - `Find-Identity` filters the group, user and service-principal caches with
      `.Where()` instead of copying each one per ACE.
    - `CacheItem` stamps its creation time with `[datetime]::Now` rather than
      a `Get-Date` call.
 
### Fixed
 
- Integration tests
  - `AzDoQueryPermission` and `AzDoWorkItemQuery` create their query folder
    through the new `Invoke-TestQueryFolderSet`. It retries the
    `TF401256: You do not have Write permissions for query Shared Queries` 403
    that a project returns for a few seconds after it reaches `wellFormed`.
    `Invoke-AzDevOpsApiRestMethod` no longer retries a 403, and that retry
    used to hide the delay.
  - `AzDoReleaseDefinitionPermission` calls `Enable-TestClassicPipeline` before
    creating its classic release definition, and skips when an organization
    policy forbids classic pipelines, as `AzDoTaskGroup` and
    `AzDoDeploymentGroup` already do.
  - `AzDoPicklist` polls for up to 30 seconds for the removed picklist to
    drop out of the picklist list. The list could still return it for a moment
    after a successful DELETE.
  - `Get-MIToken` requests its token with `-NoAuthentication`, which
    `Invoke-APIRestMethod` now honours. It used to build an Authorization
    header from `$Global:DSCAZDO_AuthenticationToken`, so after a test whose
    token request failed had left that global null, the post-run teardown
    threw before cleaning up or reporting the test results.
- AzureDevOpsDscNative
  - `Invoke-AzDevOpsApiRestMethod -AzureArcAuthentication` retries a 408, 429
    or 5xx from the Azure Arc token endpoint like any other request. It used to
    hand every error straight back to `Get-AzManagedIdentityToken`, which
    only needs the 401 challenge unchanged, so a throttled token request failed
    the resource being constructed. It showed up as a 429 failing
    `[AzDoProject]::Export()` and `[AzDoGitRepository]::Export()`.
  - `Invoke-AzDevOpsApiRestMethod` no longer throws "Unable to find type
    [APIRateLimit]" on a 429 or a response carrying rate-limit headers. The
    class lives in the root module, which the nested `AzureDevOpsDsc.Common`
    module cannot resolve type names from; it now builds the object through
    the new `New-APIRateLimit` function, as the token classes already do.
  - The nested `AzureDevOpsDsc.Common` module now imports the
    `DscResource.Common` copy shipped beside it. It called `Get-LocalizedData`
    without importing it, so the command was found through auto-discovery as an
    export of `AzureDevOpsDscNative`, which was itself still loading. Every
    process that had not already imported `DscResource.Common` globally failed
    to load the module with "the module could not be loaded". That included
    each `dsc resource get/test/set` call through the DSC v3 PowerShell adapter.
  - The identity cache initializer (`AzDoAPI_7_IdentitySubjectDescriptors`)
    re-added every group, user and service principal with the whole cache item
    as its value, so `.value.ACLIdentity` read `$null` for all of them and every
    descriptor lookup in `Find-Identity` missed the cache and fell through to
    the API. The resolved identity is now stamped onto the cached item in place.
  - `Invoke-AzDevOpsApiRestMethod` retry and rate-limit handling:
    - A 429 without a `Retry-After` header stored the retry interval
      (milliseconds) in a field read as seconds, so the next attempt slept for
      250 seconds. It now backs off exponentially in milliseconds.
    - `Retry-After` was read with a string indexer that PowerShell 7''s
      `HttpResponseHeaders` does not have, so it was never found. It is now read
      from any header shape, as seconds or an HTTP date.
    - Non-transient errors (400, 401, 403, 404, 409 and other 4xx except 408 and
      429) are no longer retried; they throw on the first failure. Network
      errors, 408, 429 and 5xx are still retried, and there is no sleep after the
      final failed attempt.
    - `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After` are now read
      from successful responses, so the "close to throttled" slow-down acts on
      what Azure DevOps reports; a `Retry-After` wait is honoured once instead of
      before every request until the next success.
    - `APIRateLimit.xRateLimitRemaining` defaults to `-1` (not reported) rather
      than `0`, which read as an exhausted budget and added an extra wait after
      every 429.
    - With `-NoAuthentication`, the caller''s own `Authorization` header was
      cleared after the first attempt, so every retry was sent unauthenticated.
      The header is now cleared only when the function added it. The
      authentication header check also tested a non-existent `Authentication`
      key; the module token header is now set explicitly before every request.
  - Fixed `Get-AzDoProject` throwing "Process template '''' not found" instead
    of naming the process that was actually missing - the "not found" error
    interpolated an unrelated, always-null variable rather tha'


        } # End of PSData hashtable

    } # End of PrivateData hashtable
}