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.3.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','AzDoDashboard','AzDoOrganizationPermission','AzDoTaskGroupPermission')

    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.3.0] - 2026-11-08
 
### Security
 
- Credential storage
  - `New-AzDoAuthenticationProvider` no longer leaves `ModuleSettings.clixml` and the cache
    directory with default permissions. A new private helper, `Protect-AzDoCachePath`,
    restricts everything the module writes under `AZDODSC_CACHE_DIRECTORY` to its owner:
    on Windows the ACL is replaced with an explicit, non-inherited DACL granting
    FullControl to the current user, SYSTEM and BUILTIN\Administrators only; on Linux and
    macOS files are set to mode 600 and directories to 700. `New-AzDoAuthenticationProvider`,
    `Export-CacheObject`, `Initialize-CacheObject` and `Save-IdentityDescriptorIndex` apply
    it to every directory they create and file they write, so the identity caches and
    `IdentityDescriptorIndex.clixml`, which decide which identity a permission grant
    resolves to, are covered as well. A permissions failure is a warning, never an error.
  - On Linux and macOS `Export-Clixml` stores a SecureString as hex-encoded UTF-16, so
    `ModuleSettings.clixml` holds the credential in a recoverable form.
    `New-AzDoAuthenticationProvider` now warns on those platforms that the file is
    protected only by the owner-only permissions it sets, that `AZDODSC_CACHE_DIRECTORY`
    must be a private path, and that DSC must run as the same account. `USAGE.md` and
    `CLAUDE.md` describe this.
  - `New-AzDoAuthenticationProvider` defaults to the `SecureStringPersonalAccessToken`
    parameter set. The plain-text `-PersonalAccessToken` and `-ClientSecret` parameters
    still work but warn that the secret lands in shell history, transcripts and process
    listings, and name the `-SecureStringPersonalAccessToken` / `-SecureStringClientSecret`
    alternative. Every example and the integration test framework use the SecureString
    parameters.
- Transport
  - `Invoke-AzDevOpsApiRestMethod` refuses any request that is not https before it builds
    an Authorization header. The only exceptions are the two instance metadata endpoints
    that answer http only: IMDS (`169.254.169.254`) and the Azure Arc agent on `localhost`.
    `Test-AzDevOpsApiUri` accepts https only.
- Azure Arc
  - `Get-AzManagedIdentityToken` validates both Azure Arc inputs before using them, as
    Microsoft''s reference implementations do. `IDENTITY_ENDPOINT` must be an absolute
    http/https URI on `localhost`, `127.0.0.1` or `::1`; the secret file path from the 401
    challenge (`WWW-Authenticate: Basic realm=<path>`) must be a `.key` file directly inside
    the Arc tokens directory (`%ProgramData%\AzureConnectedMachineAgent\Tokens` on Windows,
    `/var/opt/azcmagent/tokens` on Linux). Anything else throws, naming the value, before a
    request is sent or a file is read. New private helpers `Test-AzDoArcSecretFilePath`
    and `Get-AzDoArcTokensDirectory`.
- Token guard
  - `AuthenticationToken.TestCaller()` no longer carries the `Write-*` and `Out-File`
    checks, which could never fire (`TestCallStack` compares whole command names, and
    arguments are evaluated before a cmdlet is on the stack). The comments above it and
    above `ServicePrincipalToken.GetClientSecret()` now say what the remaining call-stack
    check is: a guard against accidental use of the plain token outside the module''s
    request path, not a security boundary. The allowed-callers behaviour is unchanged.
- Secrets in logs and exports
  - `AzDevOpsDscResourceBase` gains `FormatPropertiesForLog()`, which renders
    current-state properties for verbose logging with secret properties (those named by
    `GetDscResourceSecretPropertyNames()`) shown as `<redacted>` and hashtable-valued
    properties shown as key names only. `AzDoVariableGroup`, `AzDoServiceConnection`,
    `AzDoAuditStream` and `AzDoServiceHook` log through it, so variable values, a service
    connection''s `serviceprincipalkey` and consumer credentials no longer reach DSC
    verbose logs.
  - `AzDoServiceConnection` declares `Authorization`, and `AzDoAuditStream` and
    `AzDoServiceHook` declare `ConsumerInputs`, as secret properties, so `Export()` redacts
    them through `Protect-AzDoExportedSecretProperty` instead of writing them out.
  - `AzDoVariableGroup` can create secret variables: `Variables` accepts
    `@{ NAME = @{ value = ''v''; isSecret = $true } }` alongside `@{ NAME = ''value'' }`. A
    secret variable''s value is never read back, compared or logged.
- CI
  - The integration test workflow no longer admits pull requests from forks. Both
    `integration-tests.yml` jobs require the pull request''s head repository to be this
    repository in addition to the `resource-add/` branch prefix, and the test framework
    configuration step refuses to fall back to Managed Identity on a `pull_request` run
    (it throws if `AZURE_DEVOPS_PAT` is empty). Previously a fork''s code could have run on
    the self-hosted runner under the runner VM''s identity.
  - Both integration workflows wipe the module cache directory before the run and, in a
    final `if: always()` step, remove it again and restore
    `tests/Integration/TestFrameworkConfiguration.json` from git, so neither
    `ModuleSettings.clixml` nor the plain-text PAT survives the job on the runner.
  - `publish.yml` token permissions moved from workflow level to job level; only the
    `publish` job holds `contents: write` and `pull-requests: write`; the release-gate jobs
    on the self-hosted runners get `contents: read`.
  - `actions/checkout`, `actions/cache` and `actions/upload-artifact` are pinned to commit
    SHAs (v4.4.0, v4.3.0, v4.6.2) in every workflow.
  - New `script-analysis` job in `unit-tests.yml` runs PSScriptAnalyzer 1.24.0 over
    `source/Classes`, `source/Modules` and `source/Enum` with the security-focused rule
    set in the new `PSScriptAnalyzerSettings.psd1`; any finding fails the job.
 
### Changed
 
- Documentation
  - `docs/ResourceRoadmap.md` gains §12, the resources a code review found
    missing that neither the roadmap nor an open issue tracked: permission
    resources for the `Collection`, `MetaTask`, `ServiceHooks`, `AuditLog`,
    `EventSubscription` and `VersionControlItems` namespaces and for deployment
    groups and project agent queues; `AzDoWorkItemTemplate` and
    `AzDoTaskboardColumn`; `AzDoAgent`, agent pool maintenance and per-pipeline
    settings on `AzDoPipeline`; `AzDoTestResultRetentionSettings` and
    organization-wide feed permissions; and project properties and notification
    delivery settings. It also records what was checked and found already
    covered or not buildable, and a suggested order. §2, §8 and §9 point to it.
  - `docs/ResourceRoadmap.md` gains §13 for open issues #66 (Copilot code
    review) and #67 (GitHub Advanced Security), which the roadmap did not
    mention. It lists the proposed resources and the questions each issue
    must settle first, and gives an order. §8 and §9 point to it.
- Tests
  - The integration tests for `AzDoDashboard`, `AzDoOrganizationPermission` and
    `AzDoTaskGroupPermission` now read the live state back from the REST API
    after each `Set`, instead of relying only on the resource''s own `Test`:
    - `AzDoDashboard`: the written description and refresh interval, no
      duplicate on a repeated `Set`, a widget added outside DSC surviving an
      update, drift made outside DSC being detected and fixed, and a team and
      a project dashboard with the same name being kept apart.
    - `AzDoOrganizationPermission`: the exact allow and deny bits, and every
      other entry on the organization root compared mask for mask against a
      snapshot taken before any write - after a `Set`, a second group''s `Set`,
      a repeated `Set` and `Absent`. Also outside drift, permissions named by
      display name, and `Absent` removing only its own group''s entry.
    - `AzDoTaskGroupPermission`: the same live-ACL checks on the project-level
      token, several identities in one configuration, changing a permission
      from `Deny` to `Allow`, turning inheritance off and back on, and a
      project that does not exist.
  - `New-AzDoAuthenticationProvider.tests.ps1` uses `$TestDrive` instead of a `C:\` path,
    so it passes on Linux. The known Linux-only failure count in the Common suite drops
    from 28 to 9 (the remaining `C:\`-path tests in `Export-CacheObject.tests.ps1` and
    `Initialize-CacheObject.tests.ps1`).
 
### Fixed
 
- AzureDevOpsDscNative
  - Reloading a cache no longer writes the whole cache to the output stream
    ([issue #55](https://github.com/ZanattaMichael/AzureDevOpsDsc/issues/55)).
    `Import-CacheObject` returns the collection it imported, and
    `Initialize-CacheObject`, `Refresh-CacheObject` and `Refresh-AzDoCache`
    called it for its side effect without discarding that value. Every `New-`
    and `Set-` function that ends by refreshing its cache printed the cache as
    a `Key`/`Value` table during `Invoke-DscResource`.
  - `Get-AzDoProjectServices` reports `NotFound` for a project that does not
    exist, rather than failing with `TF200016`. Its live fallback lookup now
    goes through `Resolve-AzDoProject`, which treats a 404 as no project.
  - SecureStrings are decoded correctly on Linux and macOS. `PtrToStringAuto` reads a BSTR
    as UTF-8 off Windows and returned garbage in `Add-AuthenticationHTTPHeader`
    (managed-identity cache restore), `Get-AzManagedIdentityToken` (cached token reuse),
    `Get-AzServicePrincipalCertificateToken` (the PFX password, so the PFX path did not
    work on Linux at all) and `AzDevOpsDscResourceBase.Construct()` (Workload Identity
    Federation manual-token restore). All of them, and `Get-AzServicePrincipalToken`, now
    use the new private helper `ConvertFrom-AzDoSecureString`.
  - `Get-AzDoVariableGroup` detects drift in `Description` and `Variables` ('


        } # End of PSData hashtable

    } # End of PrivateData hashtable
}