diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..f4fa8ec --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,31 @@ +# CODEOWNERS — auto-requests review from the listed owners on matching paths. +# Syntax: https://docs.github.com/articles/about-code-owners +# Owners are the team @RWS/ish plus named maintainer @ddemeyer (so there is always at +# least one individual). The team and the user must have Write access, otherwise GitHub +# silently ignores the rule. + +# Default owner for everything in the repo (the "implementer" the instructions refer to). +* @RWS/ish @ddemeyer + +# --- Contract-sensitive surfaces: changes here need implementer approval --- + +# Public object model = backward-compatibility contract (PS1 scripts + Pester bind to it), +# tightly correlated with ISHRemote.Format.ps1xml. +/Source/ISHRemote/Trisoft.ISHRemote/Objects/ @RWS/ish @ddemeyer +/Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.Format.ps1xml @RWS/ish @ddemeyer + +# Self-contained authentication + proxy stack (WS-Trust / OpenID Connect over SOAP & REST). +/Source/ISHRemote/Trisoft.ISHRemote/Connection/ @RWS/ish @ddemeyer + +# Cmdlet surface: names, parameters and parameter sets are public contract. +/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/ @RWS/ish @ddemeyer + +# Public PowerShell functions that ship inside the module (exported library surface). +/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/ @RWS/ish @ddemeyer + +# Build, versioning and packaging. +/Source/ISHRemote/Directory.Build.props @RWS/ish @ddemeyer +/Source/ISHRemote/Trisoft.ISHRemote/Trisoft.ISHRemote.csproj @RWS/ish @ddemeyer + +# Repo guidance for humans and the cloud agent. +/.github/ @RWS/ish @ddemeyer diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..c65319e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,101 @@ +name: Bug report +description: Report a problem with an ISHRemote cmdlet, authentication, or behavior. +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to file a bug. Please **anonymize** everything you paste: + no real server URLs, usernames, passwords, client ids/secrets, or tokens — use + `https://example.com/ISHWS/` style placeholders. + - type: textarea + id: what-happened + attributes: + label: What happened (actual result) + description: What did you observe? Include the exact error message text if any. + placeholder: e.g. Get-IshDocumentObj throws "[-14] The access is denied ..." + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected result + description: What did you expect to happen instead? + validations: + required: true + - type: textarea + id: repro + attributes: + label: Minimal repro script (anonymized) + description: The smallest cmdlet sequence that reproduces it. Assume an existing `$ishSession`. + render: powershell + placeholder: | + # New-IshSession -WsBaseUrl "https://example.com/ISHWS/" ... + Get-IshFolder -BaseFolder Data + validations: + required: true + - type: input + id: ishremote-version + attributes: + label: ISHRemote version + description: "`(Get-Module ISHRemote).Version` or `$ishSession.ClientVersion`" + placeholder: e.g. 8.2.13525.0 + validations: + required: true + - type: textarea + id: powershell-host + attributes: + label: PowerShell host & version + description: "Output of `$PSVersionTable` (PSEdition, PSVersion, OS)." + render: text + placeholder: e.g. PSEdition Core, PSVersion 7.6.0 on .NET 10.0.5, Windows + validations: + required: true + - type: input + id: server-version + attributes: + label: Tridion Docs / InfoShare server version + description: "`$ishSession.ServerVersion`, e.g. 14.0.4, 15.2.0, 15.3.0" + placeholder: e.g. 15.3.0 + validations: + required: true + - type: dropdown + id: protocol + attributes: + label: Protocol + description: "`$ishSession.Protocol` — which connection protocol was in use?" + options: + - "WcfSoapWithWsTrust (legacy, ≤14.x / forced)" + - "WcfSoapWithOpenIdConnect (15.x SOAP over OpenID Connect)" + - "OpenApiWithOpenIdConnect (REST)" + - "Not sure" + validations: + required: true + - type: dropdown + id: auth-flow + attributes: + label: Authentication flow + options: + - "Interactive System Browser (Authorization Code + PKCE)" + - "Client Credentials (-ClientId / -ClientSecret)" + - "Username/Password or NetworkCredential (WS-Trust)" + - "Not sure" + validations: + required: false + - type: textarea + id: logs + attributes: + label: Verbose / debug output (anonymized) + description: Re-run with `-Verbose` (and `-Debug` if useful) and paste the relevant lines. Scrub URLs/secrets/tokens. + render: text + validations: + required: false + - type: checkboxes + id: confirm + attributes: + label: Before submitting + options: + - label: I removed all real URLs, credentials, client secrets, and tokens from this report. + required: true + - label: I searched existing issues for a duplicate. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c3fa4ca --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: true +contact_links: + - name: RWS Community — Tridion Docs Developers forum + url: https://community.rws.com/product-groups/tridion-docs/ + about: Questions about usage, the "Web Services API", or Tridion Docs itself (not ISHRemote bugs). + - name: Report a security vulnerability + url: https://community.rws.com + about: Please do not file public issues for security reports — see SECURITY.md and reach out here. + - name: ISHRemote documentation & release notes + url: https://github.com/RWS/ISHRemote/tree/master/Doc + about: Installation, ReleaseNotes, ThePlan and TheExecution background reading. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..df15ddf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,79 @@ +name: Feature request / idea +description: Propose a new cmdlet, parameter or capability — or just drop an idea from your notes. +title: "Add cmdlet Verb-IshNoun to " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Drop the idea like a design note — write as much or as little as you like (a blank free-text + issue is fine too). Roadmap reasoning lives in `Doc/ThePlan-ISHRemote-*.md` and + `Doc/TheExecution-ISHRemote-*.md`, and feature notes in this style are issues like #126, #120, + #70 and #229. + - type: textarea + id: idea + attributes: + label: Idea + description: | + A quick drop of the idea — what should ISHRemote be able to do, and what are you trying to + automate? What's painful or impossible today? Link any community.rws.com thread. + placeholder: | + Similar to Get-IshFolderContent, people want to know which IshDocumentObj are directly + linked to an IshPublicationOutput (baseline-selected versions only, no LatestAvailable + completion). + validations: + required: true + - type: textarea + id: today + attributes: + label: What's possible today (starting point / workaround) + description: | + The closest existing cmdlet to start from, and how you work around the gap now — a raw + `$ishSession.25.(...)` SOAP call is a perfect illustration. + placeholder: | + Starting point is Get-IshPublicationOutputData (it downloads the generated CHM/PDF/ZIP). + There is no cmdlet yet that returns the *content objects* used by the publication. + validations: + required: false + - type: textarea + id: proposal + attributes: + label: Proposed cmdlet(s) and parameters + description: | + Sketch the shape. Conventions: `Verb-IshNoun` (approved verb), **singular** parameter names, + `-WhatIf`/`-Confirm` on writes, parameter *groups* (sets) like `IshObjectGroup` / + `ParameterGroup` with mandatory/optional + defaults, and typed `Ish*` pipeline output. If you + know the backing Web Services API function, name it — SOAP `EventMonitor25.DeleteByProgressId` + or OpenAPI `OpenApiISH30Client.GetDocumentObjectListByLogicalIdAsync`. New work biases toward + `OpenApiWithOpenIdConnect` where server parity exists (15.3.0+). + render: powershell + placeholder: | + Remove-IshEvent + # IshEventGroup (preferred — respects incoming pipeline objects) + -IshEvent # mandatory, typically from Get-IshEvent + -DeleteFailedEvent # optional, default $true + # ParameterGroup + -ProgressId # note: ProgressId is not stable API surface + # backs onto EventMonitor25.DeleteByProgressId + validations: + required: false + - type: textarea + id: ideas + attributes: + label: Ideas, spikes & related issues + description: | + Optional parameters, future spikes, scope you explicitly exclude, related issues (#nnn), and + which `*.Tests.ps1` already holds suitable multi-version test data to extend. + placeholder: | + - Optional -AutoCompleteMode (LatestAvailable/LatestReleased) for a later iteration. + - Test data: see AddBackgroundTask.Tests.ps1 / AddIshPublicationOutput.Tests.ps1. + - Related: #222. + validations: + required: false + - type: checkboxes + id: contribute + attributes: + label: Contribution + options: + - label: I'd be up for opening a PR for this (guidance is welcome). + required: false diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..e40e20c --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,214 @@ +# ISHRemote — Copilot Cloud Agent Instructions + +Trust these instructions first. Only search the codebase if something here is incomplete or proven wrong. + +## What this repository is +ISHRemote is a **binary PowerShell module** for business automation on top of RWS Tridion Docs +Content Manager (InfoShare CMS). It is a thin client over the CMS "Web Services API" (WCF SOAP + +OpenAPI protected by OpenID Connect) and is published to the PowerShell Gallery. Most cmdlets are C# classes; +a few advanced functions are PowerShell scripts. Cmdlets follow `Verb-IshNoun` naming. + +- **Languages/runtimes:** C# multi-targeted `net48;net6.0;net10.0`; PowerShell 5.1 (Windows + PowerShell / .NET Framework 4.8) and PowerShell 7+ (CoreCLR, .NET 6/10). +- **Size:** Medium. 3 C# projects, ~150+ cmdlet `.cs` files, ~58 `*.Tests.ps1` Pester files. +- **Build is Windows-only.** Targeting `net48` plus MSBuild targets that invoke `powershell.exe` + (Windows PowerShell 5.1) to generate the module manifest mean a full build requires Windows + + Visual Studio 2022 / MSBuild. CI runs on `windows-latest`. + +## Project layout (where to make changes) +- `Source/ISHRemote/ISHRemote.sln` — the solution (3 projects). +- `Source/ISHRemote/Directory.Build.props` — **version numbers, .NET analyzers, and + `TreatWarningsAsErrors=true` for `Release`**. Read this before touching versions or warnings. +- `Source/ISHRemote/Trisoft.ISHRemote/` — the main module project (`Trisoft.ISHRemote.csproj`): + - `Cmdlets//` — C# cmdlets grouped by domain (User, Folder, DocumentObj, Session, + Settings, etc.). Each cmdlet is `XxxIshYyy.cs` with a matching `XxxIshYyy.Tests.ps1` and a + per-domain base class (e.g. `UserCmdlet.cs`). **Add a new cmdlet here + its `.Tests.ps1`.** + - `Objects/`, `Connection/`, `HelperClasses/`, `ExtensionMethods/`, `Interfaces/`, `Exceptions/`. + - `Scripts/Public/` and `Scripts/Private/` — PowerShell advanced functions (MCP server, + parameter completion). **These are what PSScriptAnalyzer lints.** + - `ISHRemote.psm1` — root module; loads the right `net48`/`net6.0`/`net10.0` DLL by PS edition. + - `ISHRemote.Format.ps1xml` — output formatting. + - `ISHRemote.PesterSetup.ps1` — central test setup, dot-sourced by every `*.Tests.ps1`. + - `ISHRemote.PesterSetup.Debug.ps1` — git-ignored local test credentials/overrides. +- `Source/ISHRemote/Trisoft.ISHRemote.OpenApiAM10/` and `.OpenApiISH30/` — `netstandard2.0` + NSwag-generated OpenAPI clients. Generated code; do not hand-edit the `*.json`-derived clients. +- `.editorconfig` — CRLF, final newline, indent 2 spaces (C# and `.ps1` use 4). + +## How to build (validated against CI — always do these in order) +1. **Always restore the whole solution first** (fixes the transient multi-target `ProjectReference` + errors `CS0234`/`CS0518` described in README; restoring the `.sln` is enough): + ``` + dotnet restore Source/ISHRemote/ISHRemote.sln + ``` +2. **Build:** + ``` + dotnet build --no-restore --no-incremental --configuration release Source/ISHRemote/ISHRemote.sln + ``` + - Use `Debug` instead of `Release` for local iteration; the Pester tests load `bin\debug\ISHRemote` + when not in CI and `bin\release\ISHRemote` when `$env:GITHUB_ACTIONS -eq "true"`. + - **Release fails on any compiler/analyzer/XML-doc warning** (`TreatWarningsAsErrors=true` in + Directory.Build.props). Keep the tree warning-clean. `NoWarn=1591` already suppresses + "missing XML comment". + - If the first build ever fails with `CS0234`/`CS0518` (namespace/predefined type not found), + just **restore and build again** — it resolves once project dependencies are restored. + - The `net48` target runs extra MSBuild targets (XmlDoc2CmdletDoc help generation, module + manifest, packaging into `bin\\ISHRemote\` with `net48`/`net6.0`/`net10.0` subfolders). + These require **both** `pwsh.exe` (PowerShell 7) and `powershell.exe` (Windows PowerShell 5.1). + +## How to lint (validated against CI) +```powershell +Set-PSRepository PSGallery -InstallationPolicy Trusted +Install-Module PSScriptAnalyzer -ErrorAction Stop +Invoke-ScriptAnalyzer -Path Source/ISHRemote/Trisoft.ISHRemote/Scripts -Recurse +``` +CI reports analyzer warnings/errors but does **not** fail the build on them. Still, keep +`Scripts/**` clean. + +## How to test (Pester) — important constraints +- **The Pester suite is acceptance/integration tests that require a LIVE Tridion Docs server.** For + local runs put the endpoint/credentials in `ISHRemote.PesterSetup.Debug.ps1` (git-ignored, the + source of truth locally). The `ISH_BASE_URL`, `ISH_USER_NAME`, `ISH_PASSWORD`, `ISH_CLIENT_ID`, + `ISH_CLIENT_SECRET` env vars are for CI (GitHub secrets) — don't override local debug settings + with them for normal local runs. Without a reachable server, **most tests cannot pass** — do not + assume a green run locally. +- **Build before testing.** Tests import the compiled module from `bin\debug\ISHRemote` (local) or + `bin\release\ISHRemote` (CI, when `$env:GITHUB_ACTIONS -eq "true"`). `TestPrerequisite.Tests.ps1` + verifies the packaged module (`.psd1`, `Trisoft.ISHRemote.dll-Help.xml`, and + `net48`/`net6.0`/`net10.0` folders) exists. +- Use **Pester 5.3.0+** (not the Windows-bundled 3.4.0): `Install-Module -Name Pester -Force + -SkipPublisherCheck`. +- **Run order (fast → broad), from repo root:** + 1. Prerequisite check first: + `Invoke-Pester -Path Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/_TestEnvironment/TestPrerequisite.Tests.ps1 -Output Detailed` + 2. Targeted while iterating — a single file or one domain folder: + `Invoke-Pester -Path Source/ISHRemote/Trisoft.ISHRemote/Cmdlets// -Output Detailed` + 3. Full CI-pattern suite only when needed (note the excluded slow test): + `Invoke-Pester -Path @('Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/','Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/') -ExcludePath @('*GetIshDocumentObj.Tests.ps1') -Output Detailed` +- **Match CI per shell:** under `pwsh` run both `Scripts/Public` and `Cmdlets`; under Windows + PowerShell 5.1 (`powershell`) run `Cmdlets` only. Both exclude `*GetIshDocumentObj.Tests.ps1`. + +## CI pipeline — three workflow files +There are three separate workflows, each with its own trigger scope: + +**`continuous-integration.yml`** (`windows-latest`, secrets available) — triggers on push/PR to +`master` touching `Source/**`, `*.TXT`, `*.MD`. Two jobs: +1. **`build`** (fast lane, no live-server secrets): UpdatePWSH → checkout → `dotnet restore + Source/ISHRemote/ISHRemote.sln` → `dotnet build --no-restore --no-incremental --configuration + release` → PSScriptAnalyzer on `Scripts/` → Pester anonymization check → upload compiled module + artifact. +2. **`live`** (`needs: build`, `concurrency: run-tests-on-ishbaseurl`, queues — never cancels): + downloads artifact → Pester PS7 (Scripts/Public + Cmdlets, excludes `*GetIshDocumentObj*` and + `*TestAnonymization*`) → Pester PS5.1 (Cmdlets only, same excludes) → optional PSGallery + publish when commit message contains `[PublishToPSGalleryAsPreview]` or + `[PublishToPSGalleryAsRelease]`. + +**`code-quality.yml`** (`ubuntu-latest`, no secrets) — triggers on push/PR touching `Doc/**`, +`Samples/**`, `*TestAnonymization.Tests.ps1`, or the workflow file itself. Single job: installs +Pester 5.3+, runs `TestAnonymization.Tests.ps1`, throws on any failure. No build, no live server. + +**`codeql.yml`** (`ubuntu-latest`, no secrets) — triggers on push/PR touching `Source/**/*.cs`, +the workflow file, plus a weekly Monday schedule. `build-mode: none` (scans sources directly +without a Windows build). Requires Code Scanning enabled in repo Settings. + +**To replicate CI confidence before opening a PR: restore → Release build (must be warning-clean) → +PSScriptAnalyzer on Scripts.** That is what the agent can realistically validate without a CMS. + +## Issue & pull request titles (and dependency PRs) +Issues and PRs share one title style — an issue title typically becomes the PR title. The shape is +**` [with/to ] [for/to ]`**. Derive new titles from the existing tracker rather than inventing a style. +- **Feature/Task verbs seen in the tracker:** `Add`, `Augment`, `Enhance`, `Extend`, `Enable`, + `Replace`, `Refresh`, `Update`, `Rewrite`, `Improve`, `Maintain`. Name the cmdlet or family + (`*-IshTranslationJob`), then the specific parameter/capability, then the purpose. Examples: + *"Add cmdlet Remove-IshEvent"* (#120); *"Augment Get-IshDocumentObj with protocol + OpenApiWithOpenIdConnect implementation"* (#229); *"Extend Set-IshBaselineItem to update a baseline + using the incoming IshDocumentObj versions"* (#222). +- **Bug titles describe the observable symptom and the condition:** *"Get-IshDocumentObjData does not + trim long FTITLE on PS 5.1 and when no trailing slash for FolderPath is provided"* (#238). +- **Before→after changes spell out both values:** *"Update ISHRemoteMcpServer MCP Protocol Version + from '0.3.0' to '2024-11-05'"* (#228); *"Replace package references of IdentityModel.OidcClient to + Duende.IdentityModel.OidcClient"* (#220). +- **Commit prefixes** (from `.github/dependabot.yml`): `deps` for NuGet, `ci` for GitHub Actions. +- **Dependabot PR titles** — keep the repo's before→after style: lead with the **single most + significant (most security-relevant / most vulnerable) library** and its `from X to Y` bump, then + list the **before→after for every changed or added library** in the grouped PR body. Mirror that + same aggregated before→after list into the ReleaseNotes `## Dependencies` section (see + `.github/instructions/doc--markdown.instructions.md`). + +## Runtime architecture (how it fits together) +- `IshSession` (`Objects/Public/IshSession.cs`) is the central runtime object: it loads + `connectionconfiguration.xml`, selects a protocol (`WcfSoapWithWsTrust`, + `WcfSoapWithOpenIdConnect`, `OpenApiWithOpenIdConnect`), manages the token/connection lifecycle, + and exposes both SOAP proxies (the `*25` clients, e.g. `IshSession.User25`) and OpenAPI clients + (`OpenApiISH30Client`, `OpenApiAM10Client`). SOAP is still initialized even under OpenAPI because + many cmdlets still depend on it. The generated SOAP proxy classes live in `Service References/` + and are the primary contractual truth for API 2.5 calls; `OpenApiISH30.json` (≈1 MB) is the + contractual truth for API 3.0. Which call to use, how to read the compatibility table, and how to + navigate live docs is documented in + `.github/instructions/source-api-webservices--csharp.instructions.md`. +- `ISHRemote.psm1` loads the runtime-matching DLL: PowerShell Desktop → `net48`; PowerShell + 7.2–7.5 → `net6.0`; PowerShell 7.6+ → `net10.0`. +- The `net48` build packages everything into `bin//ISHRemote/` (framework subfolders + + `Scripts/` + generated `ISHRemote.psd1` and `Trisoft.ISHRemote.dll-Help.xml`); that folder is the + published module and exactly what the tests import. +- MCP server/tool registration lives in `Scripts/Public` (`Start-IshRemoteMcpServer`, + `Register-IshRemoteMcpTool`, `Invoke-IshRemoteMcpHandleRequest`) and is wired for local use via + `.vscode/mcp.json`. +- Tridion Docs domain knowledge (entity hierarchy, field types, filter operators, MCP agent rules) + is single-sourced in `Doc/McpInstructions-ISHRemote.md`; read it when working on any MCP, + cmdlet, or object code that involves CMS entities or field definitions. At module build time + this file is copied next to `Register-IshRemoteMcpInstructions.ps1` in `Scripts/Public/`. + +## Legacy & where to invest less (protocol direction) +ISHRemote must keep working across a wide range of InfoShare versions, but **not all protocols get +equal future investment.** When adding or rewiring functionality, bias new work toward the OpenAPI +path and treat the older paths as maintain-only. +- **`WcfSoapWithWsTrust` (WS-Trust / WS-Federation) — legacy, maintain-only.** It works and is the + primary protocol on **older InfoShare (e.g. 13.0.2, 14.0.4)**, so keep it functioning, but it is + **not extended** — bug-fix only. The product **removes it in 16.0.0**. +- **SOAP proxies are deprecated by the product.** Most cmdlets are still SOAP-based; that is history, + not direction. `WcfSoapWithOpenIdConnect` reaches **many customers across 15.x** and **remains in + 16.0.0**, so it is a pragmatic target — but it is **deprecated**. +- **`OpenApiWithOpenIdConnect` (REST) is the future — invest here.** Surface arrived in 15.0.0, + was enriched in 15.1.0/15.2.0, and reaches **functional parity with `WcfSoapWithOpenIdConnect` at + 15.3.0**. When an OpenAPI implementation is missing, `IshSession` falls back to + `WcfSoapWithOpenIdConnect`, so new/rewired cmdlets should prefer OpenAPI where server parity exists + and fall back deliberately. When unsure which path a change should take, **ask the implementer.** + +### Rarely-touched, low-investment files (don't treat as current) +- `Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.SignAndPublish.ps1` (and the git-ignored + `…SignAndPublish.Debug.ps1`) — manual sign-and-publish helper; almost never run (CI publishes via + the `[PublishToPSGalleryAsPreview]`/`[PublishToPSGalleryAsRelease]` commit-message trigger). Don't + wire it into normal flows. +- `Source/Tools/` (`Add-SDLOpenSourceHeader.ps1` / `Test-SDLOpenSourceHeader.ps1`) — license-header + tooling, run rarely / ad-hoc. +- `BACKLOG.MD` — the **pre-GitHub** backlog; historical, superseded by GitHub Issues. Don't treat it + as the current roadmap. + +## C# cmdlet conventions & patterns +Full authoring rules live in +[`source-cmdlets--csharp.instructions.md`](instructions/source-cmdlets--csharp.instructions.md) +(auto-injected when editing `Cmdlets/**/*.cs`). The reviewer checklist lives in +[`source-codereview-csharp.instructions.md`](instructions/source-codereview-csharp.instructions.md) +(auto-injected for all `Trisoft.ISHRemote/**/*.cs`). + +Pester test authoring rules live in +[`source-cmdlets--pester.instructions.md`](instructions/source-cmdlets--pester.instructions.md) +(auto-injected for `**/*.Tests.ps1`). The reviewer checklist lives in +[`source-codereview-pester.instructions.md`](instructions/source-codereview-pester.instructions.md). + +Scripts/PowerShell authoring rules live in +[`source-scripts--powershell.instructions.md`](instructions/source-scripts--powershell.instructions.md) +(auto-injected for `Scripts/**/*.*`). The reviewer checklist lives in +[`source-codereview-powershell.instructions.md`](instructions/source-codereview-powershell.instructions.md). + +Two build-breakers repeated here because they surface across all tasks: +- `[OutputType(typeof(IshX))]` — always `typeof`, never `nameof` (`XmlDoc2CmdletDoc` crashes on + `nameof` at `net48` build time). +- Any `[Parameter]` property **must have a getter**; setter-only silently breaks help generation. + +## Don't +- Don't add a `global.json` (none exists; the build relies on the SDKs present on the runner). +- Don't hand-edit generated OpenAPI client code or commit `ISHRemote.PesterSetup.Debug.ps1`, + `launchSettings.json`, or `*.psd1` (git-ignored / generated). +- Don't expect `dotnet test` — there are no MSTest/xUnit projects; testing is Pester only. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3c86b4b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,42 @@ +# Dependabot keeps NuGet packages and GitHub Actions patched, opening grouped PRs to limit noise. +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-reference +version: 2 +updates: + # GitHub Actions referenced by the workflows in .github/workflows/ + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "sunday" + time: "03:00" + timezone: "Europe/Brussels" + labels: + - "dependencies" + commit-message: + prefix: "ci" + groups: + github-actions: + patterns: + - "*" + + # NuGet packages across the multi-target solution (net48;net6.0;net10.0). + # Security advisories (e.g. the System.Security.Cryptography.Xml NU1903) surface here too. + - package-ecosystem: "nuget" + directory: "/Source/ISHRemote" + schedule: + interval: "weekly" + day: "sunday" + time: "03:00" + timezone: "Europe/Brussels" + open-pull-requests-limit: 10 + labels: + - "dependencies" + commit-message: + prefix: "deps" + groups: + nuget-minor-and-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" diff --git a/.github/instructions/doc--markdown.instructions.md b/.github/instructions/doc--markdown.instructions.md new file mode 100644 index 0000000..daae151 --- /dev/null +++ b/.github/instructions/doc--markdown.instructions.md @@ -0,0 +1,141 @@ +--- +applyTo: "Doc/**/*.md" +description: "Structure and intent of the ISHRemote Doc/ markdown: human-readable ReleaseNotes (clean, concise, anonymized — they become blog posts) and the ThePlan / TheExecution thinking-out-loud documents that make roadmap reasoning and GitHub issue clustering visible on the public repository." +--- + +# ISHRemote `Doc/` Markdown Conventions + +`Doc/` holds the **human-readable**, public-facing narrative of the project — not API help (that is +generated from the C# triple-slash comments). Everything here is read by customers, partners and +search engines, and the ReleaseNotes in particular get republished as **blog posts**. Write for that +audience: clear prose, concise runnable examples, and **fully anonymized** content. + +There are four document families, each one file per minor release line, all sharing the file-name +pattern `-ISHRemote-..md`: +- `ReleaseNotes-ISHRemote-*.md` — what shipped, with samples and screenshots (→ blog posts). +- `ThePlan-ISHRemote-*.md` — forward-looking thinking: the problem, the plan, milestones/stories. +- `TheExecution-ISHRemote-*.md` — a working log of how the plan is actually being built. +- `Installation-ISHRemote-*.md` — practical install/upgrade/uninstall guide per PowerShell host. + +When adding a new release line, **copy the newest file of the same family and adapt it** — don't +invent a new shape. Keep the section order and headings consistent with the predecessor so readers +(and the blog pipeline) get a predictable document. + +## 0. Golden rule — anonymize everything (no secrets, no real URLs) +These pages are public and become blog posts, so **never** paste anything customer- or +tenant-specific. Use the established placeholders already used throughout `Doc/`: +- **Server URLs:** `https://example.com/ISHWS/` or `https://ish.example.com/ISHWS/`. Never a real + customer/host/load-balancer URL. The only "real" URLs allowed are public ones: github.com/rws, + docs.rws.com, learn.microsoft.com, and similar reference links. +- **Credentials:** generic demo values only — `-PSCredential Admin`, `-IshUserName admin + -IshPassword admin`. +- **Client Credentials:** mask them — `-ClientId "c82..." -ClientSecret "ziK...=="`. Never a full + client id or secret, token, or JWT. +- **Machine / server names** (e.g. in the QA performance table): mask to a stub like `LEUDEVDDE...` + (optionally with a server build suffix `...@15.3.0b2303`). Never a full internal hostname. +- **File-system paths:** neutral examples like `C:\temp\` or `C:\Temp\API30Docs\`. +If you are tempted to show real data to make a point, stop and substitute a placeholder. + +## 1. ReleaseNotes — the blog-post document +Audience: end users upgrading. Tone: friendly, concrete, example-driven. Canonical skeleton (see +[ReleaseNotes-ISHRemote-8.3.md](../../Doc/ReleaseNotes-ISHRemote-8.3.md) and +[ReleaseNotes-ISHRemote-8.0.md](../../Doc/ReleaseNotes-ISHRemote-8.0.md)): +- `# Release Notes of ISHRemote vX.Y` then a line pointing to the high-level + [GitHub release](https://github.com/rws/ISHRemote/releases) notes ("below the most detailed + release notes we have :)"), and the **STAR the repo** call-to-action. +- `## General` — state that it inherits the prior `v0.1 … vPrev` branch and the compatibility stance + ("we expect it all to work still :)"); end with "describes the delta compared to fielded release + ISHRemote vPrev." +- `### Remember` — the standard two bullets linking to the C# source `master` tree (call out the + `Connection` protocols across `net48`/`net6.0`/`net10.0`) and to the per-cmdlet Pester tests, using + the recurring examples `AddIshDocumentObj.Tests.ps1` and `TestIshValidXml.Tests.ps1`. +- **Feature sections** — `## Sample - ` or `## Introducing <Feature>` (with `###` + sub-sections). Each is a short intro paragraph explaining *why*, then a single concise fenced + ```powershell block, then usually an **animated GIF** screenshot (see §4). Keep one idea per + sample; show the cmdlet, assume a `$ishSession` already exists. +- **Trailing standard sections** (keep these headings, write `n/a` when empty): + `## Implementation Details` (bullets with GitHub issue refs like `#210` and `Thanks @handle`), + `## Breaking Changes - Cmdlets`, `## Breaking Changes - Code`, `## Breaking Changes - Platform`, + `## Known Issues`, `## Dependencies` (the aggregated before→after table, see below), and + `## Quality Assurance` (Pester version + a performance comparison table with **anonymized** server + names) as the final section. + +### The `## Dependencies` section — aggregated before→after per library +ISHRemote pins its NuGet/runtime dependencies inline in the `.csproj` files, and bumps mostly arrive +as Dependabot PRs (see [.github/dependabot.yml](../../.github/dependabot.yml)). The ReleaseNotes carry +**one aggregated table** of every dependency that changed since the previous fielded release — added, +upgraded or removed — placed **at the back, just before `## Quality Assurance`** (the test section). +Maintain it cumulatively across the minor line: each dependency PR that lands updates or adds a row +rather than starting a fresh list, so the table mirrors the before→after data from the Dependabot PRs. +- One row per library, **before → after**. Use `n/a (new)` or `n/a (removed)` when a library is + introduced or dropped — the user explicitly wants newly introduced libraries listed too. +- **Lead with the most security-relevant** change (mirror the PR title — see + [copilot-instructions.md](../copilot-instructions.md)); list the rest alphabetically. +- Note the affected target framework when a bump is conditional (the project multi-targets + `net48;net6.0;net10.0`, e.g. `Duende.IdentityModel.OidcClient` differs per TFM). + +```markdown +## Dependencies + +| Library | Before | After | Note | +|---------|--------|-------|------| +| System.Security.Cryptography.Xml | 4.7.0 | 4.7.1 | security advisory (NU1903) — lead with the vulnerable one | +| Duende.IdentityModel.OidcClient (net10.0) | n/a (new) | 7.1.0 | replaces IdentityModel.OidcClient (#220) | +``` + +## 2. ThePlan — make the thinking visible (before) +Audience: stakeholders and contributors; purpose is to **put reasoning out in the open and cluster +it around GitHub issues** so people can react and vote. Canonical skeleton (see +[ThePlan-ISHRemote-8.3.md](../../Doc/ThePlan-ISHRemote-8.3.md) and +[ThePlan-ISHRemote-7.0.md](../../Doc/ThePlan-ISHRemote-7.0.md)): +- `# The Plan of ISHRemote vX.Y` then the standard disclaimer paragraph: *"This plan brings together + input from several stakeholders … not set-in-stone and will evolve … please let us know what you + think!"* +- `# TLDR (Too Long; Didn't Read)...` — a few sentences capturing the whole intent. +- `## The problem...` → `## The plan...` — frame the why before the what. +- Then the work, as either `## Milestone - <name>` or `## Story - <name>` (and/or an + `## The algorithm...` walk-through). It is fine and expected that **code blocks here are aspirational** + — they may reference cmdlets/parameters that don't exist yet; that's the proposal. +- Track intent inline with **task checklists** (`- [ ]` / `- [x]`) and **status suffixes/tags** on + headings (e.g. `...DONE`). +- Close with `## Suggestions...` inviting a 👍 vote on the relevant GitHub issue. +- Reference GitHub issues by `#NNN` and cluster related reasons together rather than scattering them. + +## 3. TheExecution — the working log (during/after) +Audience: future-self and the curious; purpose is to **trace how a decision was reached**. It reads +as a stream-of-consciousness build log (the author notes it's done in free time). Canonical skeleton +(see [TheExecution-ISHRemote-8.0.md](../../Doc/TheExecution-ISHRemote-8.0.md)): +- `# The Execution of the plan of ISHRemote vX.Y` then the standard intro: *"This page will try to + track work in progress … help trace how I got where I am … Inspired by [ThePlan-…]"* — link back + to the matching `ThePlan` (and prior `TheExecution`). +- Organize by `# Problem: <…>` / `## Analysis` blocks and proposal sections carrying **decision tags** + in the heading: `[APPROVED]`, `[DENIED]`, `[PROBABLY]`, `[BACKLOG]`, or `IDEA:` / `IDEA: … [BACKLOG]`. +- Cite the **research** that informed a choice — external links to learn.microsoft.com, RFCs, + StackOverflow, AWS docs, etc. — this is where the "why" lives. +- It's acceptable to leave dangling thoughts, dead-ends, and superseded ideas; that's the point of a + visible execution trace. Cross-link to `ThePlan` and ReleaseNotes when an item lands. + +## 4. Shared conventions (all `Doc/` files) +- **Images** live in [Doc/Images/](../../Doc/Images) and are referenced relatively as + `./Images/ISHRemote-<version>--<DescriptiveName>.gif`. Prefer **animated GIFs** for cmdlet demos; + give alt text a descriptive name plus a size hint (e.g. `... 1024x512`). Screenshots must also be + anonymized (no real URLs/usernames on screen). +- **Product & tech naming** — be consistent: "Tridion Docs"; dual version forms like `14SP4/14.0.4`, + `15/15.0.0`, `15.1/15.1.0`; component acronyms `ISHWS`, `ISHAM`, `ISHID`, `ISHSTS`, `ISHCM`, + `ISHCS`, `OWCF`; protocols spelled exactly `WcfSoapWithWsTrust`, `WcfSoapWithOpenIdConnect`, + `OpenApiWithOpenIdConnect`. +- **GitHub references** — issues/PRs as `#NNN`; thank contributors as `Thanks @handle`; link the repo + under the `rws`/`RWS` org. +- **Code fences** — use ```powershell for cmdlet examples (the common case); examples should be + copy-pasteable after the reader has their own `$ishSession`. Keep them short. +- **Markdown hygiene** — follow the repo [.editorconfig](../../.editorconfig): CRLF line endings and a + final newline. Headings in Title Case; one `#` H1 per file. + +## 5. Don't +- Don't include real URLs, hostnames, usernames, passwords, client ids/secrets, tokens, or + customer data — anywhere, including screenshots and QA tables (see §0). +- Don't put API/cmdlet reference docs here — that help is generated from the C# triple-slash + comments, not hand-written in `Doc/`. +- Don't restructure an existing release line's document into a new shape; mirror the previous file in + the same family. +- Don't treat `ThePlan`/`TheExecution` code as a shipped contract — it is intentionally provisional. diff --git a/.github/instructions/source-api-webservices--csharp.instructions.md b/.github/instructions/source-api-webservices--csharp.instructions.md new file mode 100644 index 0000000..16501b1 --- /dev/null +++ b/.github/instructions/source-api-webservices--csharp.instructions.md @@ -0,0 +1,236 @@ +--- +applyTo: "Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/**/*.cs" +description: "How to choose the right Tridion Docs Web Services API call (API 2.5 SOAP vs API 3.0 OpenAPI) when implementing a cmdlet: the three-tier knowledge stack, how to navigate live documentation, reading the compatibility table correctly, and writing correct version guards." +--- + +# ISHRemote Web Services API Selection and Documentation + +Every ISHRemote cmdlet calls one or more server-side API methods. There are three tiers of +knowledge for those calls, in strict priority order. **Higher tier always wins on contradictions.** + +## 1. Primary source: `Service References/*/Reference.cs` (generated SOAP proxies) + +The folders under `Source/ISHRemote/Trisoft.ISHRemote/Service References/` each contain a +`Reference.cs` generated from the WCF WSDL. These are the **contractual truth** for every +API 2.5 SOAP call: C# method names, parameter names, parameter types, return types, and SOAP +action URIs. `IshSession` exposes them as `IshSession.Application25`, `.Folder25`, +`.DocumentObj25`, `.BackgroundTask25`, `.User25`, etc. + +**Always read `Reference.cs` before consulting documentation.** If anything in a documentation +page contradicts what the proxy says, the proxy is correct — docs can lag or be for a +different version. The `.xsd` files in the same folder describe the WCF message envelope +(request/response element shapes), but they type every XML-payload parameter as plain +`xs:string` — they do **not** define the XML content inside those strings. See §3 for how +to discover that content. + +What `Reference.cs` does NOT tell you (use §3 for these): +- What XML structure belongs inside `String` parameters — every payload `String` is typed as + `xs:string` in the `.xsd` files too; the actual XML shape lives in the dedicated XML structure + topics linked from the doc page description column (see §3) +- Business rules and preconditions ("Requirements are:", "Note that:") +- Algorithm pseudocode ("The algorithm could do:") +- Why a method is deprecated and what its replacement actually does differently + +## 2. Secondary source: `OpenApiISH30.json` + generated client (API 3.0) + +`Source/ISHRemote/Trisoft.ISHRemote.OpenApiISH30/OpenApiISH30.json` (≈1 MB) is the contractual +truth for API 3.0 REST calls via `IshSession.OpenApiISH30Client`. It includes typed request/ +response schemas, parameter descriptions, and required fields in a way the SOAP WSDL does not. + +## 3. Tertiary source: live docs.rws.com + +Use only for what the proxies and JSON spec do not provide. Always fetch live — never rely on a +cached or copied version of the documentation in this repo. + +### Finding the latest documentation (search-first pattern — use every time) + +Publication IDs change per Tridion Docs release. **Never hardcode a publication ID.** +Use this two-step pattern to always land on the latest version: + +**Step 1 — Find the latest publication ID:** +``` +https://docs.rws.com/en-US/search?query=%22Web+Services+API+compatibility+across+releases%22&page=1 +``` +The results list multiple Tridion Docs versions. Pick the one with the highest version number. +Its URL reveals the publication ID: `https://docs.rws.com/en-US/{pubId}/331933/...` + +Known publication IDs (for cross-reference only — always verify via search): +- Tridion Docs 15.0 → `992527` +- Tridion Docs 15.1 → `1151795` +- Tridion Docs 15.2 → `1165616` +- Tridion Docs 15.3 → `1312231` ← current latest as of 2026 + +**Step 2 — Find the topic ID for a specific method:** +The topic ID is **stable across all publications** — it never changes for a given page. Note that Genius/DXD urls are only permalink (so using the logical card id for a topic, instead of the version-aware language cardid) since Tridion Docs 15.0.0 where Publish plugin `ISHDITADELIVERYPREPAREOVERALLPACKAGE` got the `<parameter name="ContentObjectIdLevel">logical</parameter>` option. +Search by method name to retrieve it: +``` +https://docs.rws.com/en-US/search?query=%22{ServiceName}+2.5+{MethodName}%22&page=1 +``` +Examples: +- `%22Application+2.5+GetVersion%22` → topic `68528` +- `%22Baseline+2.5+ExpandReport%22` → topic `68586` +- Compatibility table → topic `331933` (always) + +**Construct the final URL:** +``` +https://docs.rws.com/en-US/{pubId}/{topicId}/tridion-docs-main-documentation/{slug} +``` +The slug is human-readable but secondary — the `pubId`+`topicId` pair is sufficient. + +### What API 2.5 method pages provide beyond the proxy + +Each `{ServiceName} 2.5 {MethodName}` page documents: +- **Parameter directions** (`In`, `Out`, `InOut`) — essential for identifying which `string` + parameters carry an XML payload you must construct (`In`) vs ones you must parse + (`Out`/`InOut`). The direction column in the parameter table is the only place this is stated. +- **The name of the XML structure** for payload strings (e.g., `psBaselineReport | String | In | + A Baseline Report XML structure`). That structure name is a **hyperlink** leading to a + dedicated XML structure topic in the docs. Each such topic has two sections: `## XML structure` + (the formal XSD schema with typed attributes and enumerations) and `## Example` (a real XML + snippet). See "Finding the XML content inside string parameters" below for how to reach those + pages when the hyperlink is not directly followable. +- **"Requirements are:"** — access rights and preconditions the server enforces +- **"Note that:"** — edge cases, fallback language logic, multi-value join rules +- **"The algorithm could do:"** — the canonical sequence of calls for a complex operation +- A "Recommendations" section linking to the successor method and related calls + +### Finding the XML content inside `string` parameters + +The SOAP API is deliberately untyped at the payload level. A parameter like +`string xmlBaselineReport` in `Reference.cs` and `<xs:element name="xmlBaselineReport" +type="xs:string" />` in the `.xsd` both confirm the parameter exists but say nothing about +what XML goes inside. The authoritative source is a set of dedicated XML structure pages in +the docs. The actual discovery path has four steps: + +1. **Follow the hyperlink from the method's parameter description column (primary source).** + On every `{ServiceName} 2.5 {MethodName}` doc page the description cell for a `String` + payload parameter is a clickable hyperlink to a dedicated XML structure topic. That topic has + two sections: `## XML structure` (formal XSD with typed attributes and enumerations) and + `## Example` (a real XML snippet). When following a link is not possible directly (e.g., when + using a tool that strips inline hyperlinks from table cells), apply the search-first pattern + using the structure name from the description: + ``` + https://docs.rws.com/en-US/search?query=%22{StructureName}%22&page=1 + ``` + For example, `psBaselineReport | String | In | A Baseline Report XML structure` → + search `%22Baseline+Report+XML+structure%22`. Pick the result whose title matches exactly + (e.g., "Baseline Report Information") and is from the highest-version publication. + These topic IDs are stable across publications — the same topic ID works for any pubId. + +2. **Cross-check against existing parsers in this codebase.** Grep `Objects/` and `Cmdlets/` for + `SelectNodes`/`SelectSingleNode`/`XmlElement`. Model classes frequently embed commented XML + examples and are reliable cross-checks when the live doc may be outdated. + `IshBaselineItem.cs` documents the `xmlBaseline` format returned by `GetBaseline`, + which is the flat list of pinned versions you pass back to `ExpandReport`: + ```xml + <baseline ref="GUID-D1C23864-304D-408D-86C0-52C5B58343BD"> + <objects> + <object ref="GUID.007DFDAD.CEFD.40F3.A75E.2C081228DC89" versionnumber="2" + author="Admin" source="save:LatestAvailable" + created="10/12/2008 15:05:09" modified="10/12/2008 15:05:09"/> + </objects> + </baseline> + ``` + +3. **Distinguish related-but-different structures.** One service can expose multiple named XML + structures sharing the same word root. `Baseline25` has at least two: `xmlBaseline` (flat + pinned-version list from `GetBaseline`) and `xmlBaselineReport` (expanded dependency + status + report from `GetReport` / `ExpandReport` / `CompleteReportByMode`). The `In`/`Out` direction + column tells you which parameter shape each method parameter carries — confirm this before + building a parser. + +4. **Read simpler sibling methods.** `Baseline25.Update` takes `xmlChanges` (a diff list) and + `GetBaseline` returns `xmlBaseline` (the full version list). Both are simpler than the report + format and their XPath patterns in existing cmdlets give reliable element/attribute names to + extrapolate from. + +These business rules survive the API 2.5 → API 3.0 migration. When implementing an API 3.0 +equivalent, read the API 2.5 doc page to ensure the same preconditions and edge cases are handled. + +## 4. Compatibility table — reading it correctly + +Canonical URL (use search-first to confirm latest pubId): +``` +https://docs.rws.com/en-US/1312231/331933/tridion-docs-main-documentation/web-services-api-compatibility-across-releases +``` + +The five version columns left-to-right represent: **14.x**, **15.0**, **15.1**, **15.2**, **15.3** at the time of writing. + +| Symbol | Meaning | +|---|---| +| `S` | Supported — call this method | +| `D` | Deprecated — callable, but a better option exists (named in the rightmost column) | +| `I` | Internal — not public; do not call | +| `-` | Not available (not yet added, or removed) | + +**Critical: `D` does NOT mean "use the API30 equivalent."** +The rightmost column names the concrete replacement. It may be another API25 method: +- `API25.Baseline.RetrieveMetadata` D → `API25.Baseline.RetrieveMetadata2` +- `API25.BackgroundTask.CreateBackgroundTaskWithStartAfter` D → `API30.CreateBackgroundTask` + +Only move to API 3.0 if the replacement column explicitly names `API30.*` AND +`IshSession.Protocol == OpenApiWithOpenIdConnect`. + +**Determining minimum server version:** +Scan the row left-to-right for the first `S`. That column is the minimum version. If the first +`S` appears in the 15.1 column, the method requires Tridion Docs ≥ 15.1 → add a +`PlatformNotSupportedException` guard (see §5). + +## 5. Decision flow: which API call to use + +For each server operation when implementing or modifying a cmdlet: + +1. Read `Reference.cs` for the API 2.5 SOAP signature. +2. Look up the method in the compatibility table. +3. Does the rightmost column name `API30.*` AND is `IshSession.Protocol == OpenApiWithOpenIdConnect` + AND does the server have the required version? + - **Yes** → use `IshSession.OpenApiISH30Client` with the typed NSwag method. + - **No** → use `IshSession.*25` SOAP proxy. Consult the API 2.5 method doc page for XML + structure and business rules. If the row shows `D`, use the named API25 replacement instead + of the deprecated call. +4. Use `switch (IshSession.Protocol)` — never a bare `if` — with explicit `case` arms for each + protocol. Fall-through from OpenAPI to SOAP must be intentional and documented. +5. Determine minimum supported version from the table → add `PlatformNotSupportedException` + guard if any column to the left of the first `S` is `-` (see §6). + +## 6. `PlatformNotSupportedException` version guard pattern + +Place in `BeginProcessing()`, immediately after session resolution. Copy this exact shape from +[`AddIshBackgroundTask.cs`](../../Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/BackgroundTask/AddIshBackgroundTask.cs): + +```csharp +if ((IshSession.ServerIshVersion.MajorVersion < 13) || + ((IshSession.ServerIshVersion.MajorVersion == 13) && (IshSession.ServerIshVersion.RevisionVersion < 2))) +{ + throw new PlatformNotSupportedException( + $"Add-IshBackgroundTask with the current parameter set requires server-side BackgroundTask API " + + $"which is only available starting from 13SP2/13.0.2 and up. " + + $"ServerIshVersion[{IshSession.ServerVersion}]"); +} +``` + +Rules: +- Use `MajorVersion` and `MinorVersion` (`ServerIshVersion` properties on `IshSession`). +- Message must include: what feature, the minimum version, and `ServerIshVersion[{IshSession.ServerVersion}]`. +- Do **not** add a guard for methods that show `S` on all ISHRemote-supported versions (≥13SP2). + The module already enforces 13SP2 as a floor — guards below that are redundant. +- Do **not** emit a `WriteWarning` and continue — throw unconditionally. The user cannot work + around a server-side method that does not exist. + +## 7. Don't + +- Don't invent XML payload content for `string` API25 parameters — read the method doc page. +- Don't assume `D` in the compatibility table means `API30.*` replacement — read the rightmost + column on that specific row. +- Don't hardcode publication IDs in comments, instruction files, or code — always use the + search-first pattern (§3) to find the current latest. +- Don't bypass the generated `Service References` proxy by constructing raw SOAP requests — + always call through the typed `IshSession.*25` client. +- Don't copy business-rule logic from an old version's doc page without verifying against the + latest version — fetch live, using the search-first pattern. +- Don't skip the `switch (IshSession.Protocol)` pattern and fall through silently from one + protocol to another — the switch must be explicit and every arm must be intentional. +- Don't make rapid successive live docs.rws.com fetches — pace requests to avoid HTTP 429 + throttling. Determine exactly which pages are needed first, then fetch them one at a time. + If a 429 is received, stop immediately and do not retry until the underlying need is + reassessed; never loop through large sets of doc pages in a single task. diff --git a/.github/instructions/source-cmdlets--csharp.instructions.md b/.github/instructions/source-cmdlets--csharp.instructions.md new file mode 100644 index 0000000..da7a2b8 --- /dev/null +++ b/.github/instructions/source-cmdlets--csharp.instructions.md @@ -0,0 +1,258 @@ +--- +applyTo: "Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/**/*.cs" +description: "Structure and conventions for ISHRemote C# cmdlet files: header, naming, parameters, parameter sets, triple-slash help, the BeginProcessing/ProcessRecord/EndProcessing lifecycle with batching, and the tuned exception handling." +--- + +# ISHRemote C# Cmdlet Conventions + +Every cmdlet is one `XxxIshYyy.cs` file under `Cmdlets/<Domain>/`. Consistency across files is the +whole point — these classes drive `Get-Help` and the day-to-day pipeline experience. Before writing +a new cmdlet, read 2–3 siblings in the same domain **and** the same verb in other domains, then +match them. When a design choice is ambiguous (a new parameter, a new parameter set, batching vs +per-record), **ask the implementer what they want** rather than guessing. + +> For the reviewer's binary pass/flag checklist see +> [`source-codereview-csharp.instructions.md`](source-codereview-csharp.instructions.md). + +> **Protocol direction (where to invest).** Most cmdlets call the SOAP `*25` proxies, which the +> product now considers **deprecated**. For new or rewired cmdlets, prefer the **OpenAPI** +> (`OpenApiWithOpenIdConnect`) path where server parity exists (15.3.0+), falling back to +> `WcfSoapWithOpenIdConnect`; keep `WcfSoapWithWsTrust` working for older InfoShare (≤14.x) but don't +> build new features solely on it. See the repo-wide `.github/copilot-instructions.md` "Legacy & +> where to invest less". When unsure which path a change should take, **ask the implementer.** + +> **Which API call to use, how to read the compatibility table, and how to navigate live docs** — +> see [`source-api-webservices--csharp.instructions.md`](source-api-webservices--csharp.instructions.md) +> (auto-injected when editing `Cmdlets/**/*.cs`). + +## 1. License header (mandatory, verbatim) +Every `.cs` starts with the Apache 2.0 header exactly as in neighbouring files — copy it, don't +re-type or "modernize" the year/entity text (it is historical and tooling checks for it, see +[Add-SDLOpenSourceHeader.ps1](../../Source/Tools/PowerShell/Add-SDLOpenSourceHeader.ps1) / +[Test-SDLOpenSourceHeader.ps1](../../Source/Tools/PowerShell/Test-SDLOpenSourceHeader.ps1)): + +```csharp +/* +* Copyright (c) 2014 All Rights Reserved by the SDL Group. +* ... Apache License, Version 2.0 ... +*/ +``` + +## 2. File / class / type naming +- File name = `Verb` + `IshNoun` with no hyphen: `Add-IshUser` → `AddIshUser.cs`; the **class name + must equal the file name**, and `[Cmdlet(VerbsCommon.Add, "IshUser", ...)]` must spell the same. +- Use the correct .NET verb constant (`VerbsCommon.Get/Add/Set/Remove/New`, `VerbsLifecycle.Stop`, + etc.) — never a raw string verb. +- The class is `sealed` and inherits the **per-domain base class** (e.g. `UserCmdlet`, + `BaselineCmdlet`), which supplies `public Enumerations.ISHType[] ISHType { get; }`. Put a new + cmdlet in the matching domain folder so it picks up that base. +- `[OutputType(typeof(IshX))]` — always `typeof(...)`, never `nameof(...)` (XmlDoc2CmdletDoc fails on + `nameof`). Write cmdlets set `SupportsShouldProcess = true`; read cmdlets set it `false`. + +## 3. Parameters — naming & shape consistency is critical +Parameter names are part of the public contract; pick the name an existing cmdlet already uses for +the same concept and reuse it **exactly**, both within the domain and across domains. +- **Singular names**, even for arrays: `IshObject`, `IshFolder`, `FilePath`, `Id`, `MetadataFilter`, + `RequestedMetadata`, `Metadata`, `Name`. Never `IshObjects`/`FilePaths`. +- **Standard types & validators (match these):** + - `IshSession IshSession` — `Mandatory = false`, repeated once per parameter set, + `[ValidateNotNullOrEmpty]`. Resolved in `BeginProcessing` (see §5), so users rarely pass it. + - Pipeline objects: `IshObject[] IshObject` — `Mandatory = true, ValueFromPipeline = true`, + `[AllowEmptyCollection]` (so an empty array is a documented no-op, not an error). + - Identifier lists: `string[] Id`, `[ValidateNotNullOrEmpty]`. + - Field arrays: `IshField[] MetadataFilter` / `IshField[] RequestedMetadata` / `IshField[] + Metadata`, `[ValidateNotNull]`. + - Scalars like `string Name` are `[ValidateNotNullOrEmpty]`. +- **Pipeline rules:** the array form bound `ValueFromPipeline = true` is what enables + `... | Verb-IshNoun`. Prefer `ValueFromPipeline` on the object array; use + `ValueFromPipelineByPropertyName = false` elsewhere unless a property bind is genuinely intended. +- Any property used as a `[Parameter]` **must have a getter** (setter-only breaks help generation). + A computed default can be a private get-only property (see `UserGroup` in `AddIshBaseline.cs`). + +## 4. Parameter sets — names carry meaning, keep them stable +Parameter set names appear in `Get-Help` syntax and disambiguation errors, so they must be +consistent and purposeful. The canonical pair, used by most Add/Set/Remove/Get cmdlets (both +**singular**, keep them that way): +- `"ParameterGroup"` — build the target from discrete parameters (`-Name`, `-Id`, `-Metadata`, + filters…). +- `"IshObjectGroup"` — accept `IshObject[]` from the pipeline. + +Domain-specific sets follow the same singular `<Concept>Group` shape (`MyMetadataGroup`, +`IshFolderGroup`, `FolderIdGroup`, `BaseFolderGroup`). Each set must be a coherent, +mutually-exclusive way to call the cmdlet — don't add a set that overlaps an existing one. Always +reuse these exact names. Set `[Cmdlet(... DefaultParameterSetName = "...")]` when more than one set +exists and one is the natural default. + +## 5. Lifecycle: BeginProcessing / ProcessRecord / EndProcessing +PowerShell calls **`BeginProcessing` once**, **`ProcessRecord` once per pipeline item**, and +**`EndProcessing` once** after the loop. Use that deliberately for throughput: +- `BeginProcessing` (once): resolve the session and nothing user-visible. Copy this block verbatim + from a sibling (e.g. `AddIshUser.cs`): + ```csharp + if (IshSession == null) { IshSession = (IshSession)SessionState.PSVariable.GetValue(ISHRemoteSessionStateIshSession); } + if (IshSession == null) { IshSession = (IshSession)SessionState.PSVariable.GetValue(ISHRemoteSessionStateGlobalIshSession); } + if (IshSession == null) { throw new ArgumentException(ISHRemoteSessionStateIshSessionException); } + ``` +- **Throughput is a primary goal.** The InfoShare API (SOAP and OpenAPI) exposes group/batch + operations, so it is usually far better to **accumulate pipeline items in `ProcessRecord`** into a + private `_retrieved...` field and **do the grouped server call(s) in `EndProcessing`**, rather than + one round-trip per item. Established cmdlets that batch this way: `GetIshFolder.cs`, + `MoveIshDocumentObj.cs`, `SetIshMetadataField.cs`, `AddIshBackgroundTask.cs`. +- Respect the server limits: split work with + `DivideListInBatches<T>(list, IshSession.MetadataBatchSize)` (objects/cards via + `IshSession.BatchSize`, metadata via `IshSession.MetadataBatchSize`, default 999) and loop the + batches. Never send one unbounded call. +- Report progress with `WriteParentProgress("...", current, total)` (and the child progress record + for sub-loops). Progress is informational — it must **not** change batching or throughput. +- Simple create/per-item cmdlets may instead do the work directly in `ProcessRecord` (see + `AddIshBaseline.cs`); choose per-record vs accumulate-then-flush based on whether a batch API + exists. If unsure which the implementer wants, ask. +- Wrap server writes in `if (ShouldProcess(target)) { ... }` for `SupportsShouldProcess = true` + cmdlets, and after writing, **retrieve and return typed `Ish*` objects** via + `WriteObject(IshSession, ISHType, ..., true)` shaped by `DefaultRequestedMetadata`. + +## 6. Triple-slash help (drives Get-Help — required) +- **Cmdlet class:** `<para type="synopsis">` + `<para type="description">` **and at least one** + `<example>` containing `<code>` and an explanatory `<para>`. Keep examples **short and concise**, + one idea each. +- **In examples, assume an existing `$ishSession`; do NOT include `New-IshSession`** (it is noise and + leaks URLs/credentials). Show the cmdlet itself, e.g. `Add-IshBaseline -Name "My baseline"` or a + one-line pipeline. +- **Every `[Parameter]` property** needs its own `<para type="description">...</para>`. Describe what + it does and any default; reuse the wording siblings use for the same parameter so help reads + consistently. + +## 7. Exception handling — tuned over years, preserve the order +Close `ProcessRecord`/`EndProcessing` with this exact catch ladder (copy from a sibling). The order +and the per-type `ErrorCategory` matter; each ends in `ThrowTerminatingError`: +```csharp +catch (TrisoftAutomationException e) { ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.InvalidOperation, null)); } +catch (AggregateException e) { var f = e.Flatten(); WriteWarning(f.ToString()); ThrowTerminatingError(new ErrorRecord(f, base.GetType().Name, ErrorCategory.NotSpecified, null)); } +catch (TimeoutException e) { WriteVerbose(...); ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.OperationTimeout, null)); } +catch (CommunicationException e) { WriteVerbose(...); ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.OperationStopped, null)); } +catch (Exception e) { ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.NotSpecified, null)); } +``` +Don't reorder, collapse, or silently swallow these. If you believe the handling can genuinely be +improved, **challenge it explicitly with the implementer** before changing it. + +## 8. Diagnostic logging density +A `-Debug` or `-Verbose` transcript must contain enough context to reconstruct what happened and +diagnose a ticket **without** requiring reproduction on a live server. + +### 8.1 Which method goes where +`TrisoftCmdlet` overrides all three write methods to prepend the class name; `WriteVerbose` also +forwards into `WriteDebug`, so every Verbose message appears in both streams: + +| What to log | Method | Visible when | +|---|---|---| +| Session origin, phase markers, per-item loop counters, payload sizes | `WriteDebug(...)` | `-Debug` | +| Empty no-ops, post-call result counts | `WriteVerbose(...)` | `-Verbose` (and `-Debug`) | +| Stack traces for `TimeoutException` / `CommunicationException` | `WriteVerbose(...)` | `-Verbose` (and `-Debug`) | +| Full `AggregateException`, OpenAPI error detail, `InnerException` | `WriteWarning(...)` | always | +| Long-running loops with a known item count | `WriteParentProgress(...)` | always (progress bar) | + +**Secrets and PII must never appear in any stream.** Passwords, client secrets, bearer tokens, and +personal data must not be logged. If you need to confirm a value was set, log its length or a +masked form instead: +```csharp +WriteDebug($"ClientSecret.Length[{ClientSecret.Length}] ClientSecret[{new string('*', ClientSecret.Length)}]"); +``` + +`WriteProgress` is UI sugar — it is **not** a substitute for Verbose/Debug log lines. + +### 8.2 Mandatory log points (copy from siblings) +**`BeginProcessing` — session confirmation** (one line, after session is resolved): +```csharp +WriteDebug($"Using IshSession[{IshSession.Name}] from SessionState.{ISHRemoteSessionStateIshSession} or in turn SessionState.{ISHRemoteSessionStateGlobalIshSession}"); +``` + +**Phase markers** before each major logical block: +```csharp +WriteDebug("Validating"); +// ... then ... +WriteDebug("Adding"); // or "Updating", "Deleting", "Retrieving" +``` + +**Empty-collection early exit** (both update and retrieve skipped): +```csharp +WriteVerbose("IshObject is empty, so nothing to update"); +WriteVerbose("IshObject is empty, so nothing to retrieve"); +``` + +**Per-item loop** — key identifier(s) and `{++current}/{total}` counter: +```csharp +WriteDebug($"Id[{ishObject.IshRef}] Metadata.length[{ishObject.IshFields.ToXml().Length}] {++current}/{IshObject.Length}"); +``` + +**Pre-call parameter summary** (for the `ParameterGroup` path, before the server call): +```csharp +WriteDebug($"Id[{Id}] metadata.length[{metadata.ToXml().Length}]"); +WriteDebug($"Finding ActivityFilter[{activityFilter}] MetadataFilter.length[{mf.ToXml().Length}] RequestedMetadata.length[{rm.ToXml().Length}]"); +``` + +**Post-operation result count** (just before `WriteObject`): +```csharp +WriteVerbose("returned object count[" + returnedObjects.Count + "]"); +``` + +**Exception catch logging** (already part of §7, repeated here for completeness): +```csharp +catch (AggregateException ae) { var f = ae.Flatten(); WriteWarning(f.ToString()); ... } +catch (TimeoutException te) { WriteVerbose("TimeoutException Message[" + te.Message + "] StackTrace[" + te.StackTrace + "]"); ... } +catch (CommunicationException ce) { WriteVerbose("CommunicationException Message[" + ce.Message + "] StackTrace[" + ce.StackTrace + "]"); ... } +catch (Exception e) { if (e.InnerException != null) { WriteWarning(e.InnerException.ToString()); } ... } +``` +For OpenAPI error responses, surface every structured field before terminating: +```csharp +catch (OpenApiISH30Exception<InfoShareProblemDetails> ex) +{ + if (ex.Result != null) + { + WriteWarning($"Status[{ex.Result.Status}] Title[{ex.Result.Title}] EventName[{ex.Result.EventName}] Detail[{ex.Result.Detail}]"); + foreach (var error in ex.Result.Errors) { WriteWarning($"ErrorEventName[{error.EventName}] ErrorDetail[{error.Detail}]"); } + } + ThrowTerminatingError(...); +} +``` + +### 8.3 Threading constraint — never call Write* from a non-pipeline thread +All PowerShell stream methods (`WriteDebug`, `WriteVerbose`, `WriteWarning`, `WriteProgress`, +`WriteObject`, `ThrowTerminatingError`) **must be called on the PowerShell pipeline thread** — the +thread PS invoked the cmdlet on. Calling them from any other thread throws +`InvalidOperationException` at runtime and is very hard to trace. + +- All OpenAPI async calls use `.GetAwaiter().GetResult()` — this blocks synchronously **on the + pipeline thread** and is the correct pattern for cmdlets. Do not introduce `await` or + `Task.Run()`. +- If an async helper must return diagnostic text, capture it in a local variable and log **after** + `.GetAwaiter().GetResult()` returns — never from inside a `.ContinueWith(...)` or + `Task.Run(...)` lambda. +- The `ILogger` injected into the Connection layer routes through `TrisoftCmdletLogger` which also + calls `WriteDebug`/`WriteVerbose` — the same pipeline-thread rule applies there. + +## 9. Building `ishfields` XML — field names and the live-server dependency + +API 2.5 `String` parameters like `xmlRequestedMetadata`, `xmlMetadata`, and `xmlMetadataFilter` +are all `<ishfields>` XML strings. **Never hand-build `<ishfields>` XML.** Use the established +helper chain (see `TrisoftCmdlet.cs` and `IshTypeFieldSetup.cs`): +- `IshSession.IshTypeFieldSetup.ToIshMetadataFields(...)` — for write metadata (`Add*`/`Set*`/`Move*`) +- `IshSession.IshTypeFieldSetup.ToIshRequestedMetadataFields(...)` — for read metadata (`Get*`/`Find*`) +- `IshSession.IshTypeFieldSetup.ToIshMetadataFilterFields(...)` — for filter metadata (`Find*`) + +These helpers validate field names, levels, and data types against `IshSession.IshTypeFieldDefinition` +and honour `IshSession.StrictMetadataPreference`. + +**Where field names, levels, and data types come from (in order of reliability):** + +1. **Sibling cmdlets in the same domain** — copy `DefaultRequestedMetadata` arrays verbatim. + Fields already used in a sibling that compiles and passes CI are correct by definition. +2. **Existing model XML comments** — classes like `IshBaselineItem.cs` embed real API response + XML as `/// <remarks>` showing field names in context. Grep `Objects/` for + `SelectNodes\|ishfields\|ishfield`. +3. **Ask the implementer** to run `Get-IshTypeFieldDefinition` on their live system: + ```powershell + Get-IshTypeFieldDefinition -IshSession $ishSession | + Where-Object { $_.ISHType -eq 'ISHModule' } | + Format-List ISHType, Level, Name, DataType, IsMultiValue + ``` + diff --git a/.github/instructions/source-cmdlets--pester.instructions.md b/.github/instructions/source-cmdlets--pester.instructions.md new file mode 100644 index 0000000..41bd2f7 --- /dev/null +++ b/.github/instructions/source-cmdlets--pester.instructions.md @@ -0,0 +1,110 @@ +--- +applyTo: "Source/ISHRemote/Trisoft.ISHRemote/**/*.Tests.ps1" +description: "How ISHRemote Pester (*.Tests.ps1) acceptance tests are structured, configured, build/clean their own data, and why they cannot run in parallel." +--- + +# ISHRemote Pester Test Conventions + +These are **Pester 5.3+ acceptance/integration tests** that run against a **live Tridion Docs +(InfoShare) tenant**. They are not unit tests — almost every `It` makes real Web Services API calls, +creates server objects, asserts, and then deletes them. Keep new tests consistent with the patterns +below or you will create flaky runs for everyone. + +> For the reviewer’s binary pass/flag checklist see +> [`source-codereview-pester.instructions.md`](source-codereview-pester.instructions.md). + +## Required file anatomy +Every `*.Tests.ps1` follows the same skeleton; copy it from a sibling (e.g. `AddIshUser.Tests.ps1`, +`AddIshFolder.Tests.ps1`): + +```powershell +BeforeAll { + $cmdletName = "Verb-IshNoun" # exact cmdlet under test + . (Join-Path (Split-Path -Parent $PSCommandPath) "\..\..\ISHRemote.PesterSetup.ps1") + Write-Host ("Running "+$cmdletName+" Test Data and Variables initialization") +} + +Describe "Verb-IshNoun" -Tags "Create" { # Tag = Create | Read | Update | Delete + BeforeAll { <# build the shared test data this Describe needs #> } + Context "Verb-IshNoun ParameterGroup" { + It "Parameter IshSession invalid" { { Verb-IshNoun -IshSession "INVALIDISHSESSION" } | Should -Throw } + It "GetType().Name" { $ishObject.GetType().Name | Should -BeExactly "IshX" } + } +} + +AfterAll { + Write-Host ("Running "+$cmdletName+" Test Data and Variables cleanup") + <# delete everything this file created #> +} +``` + +Rules: +- The top `BeforeAll` **must** set `$cmdletName` and dot-source + [ISHRemote.PesterSetup.ps1](../../Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.PesterSetup.ps1) + using the relative `\..\..\` hop (every test file is two folders below the project root). +- Name the file `XxxIshYyy.Tests.ps1` next to its `XxxIshYyy.cs`; `Describe` is the public cmdlet + name; tag it with the CRUD verb (`Create`/`Read`/`Update`/`Delete`). +- Reuse the shared `$ishSession` created by the setup; pass `-IshSession $ishSession` explicitly and + also cover the implicit-session and pipeline parameter sets, mirroring existing files. + +## Test environment & the Debug override (tenant + list-of-values) +[ISHRemote.PesterSetup.ps1](../../Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.PesterSetup.ps1) is +the central, committed setup. It imports the built module (`bin\debug\ISHRemote` locally, +`bin\release\ISHRemote` when `$env:GITHUB_ACTIONS -eq "true"`) and declares **shared, tenant-specific +variables** that tests depend on, e.g. `$folderTestRootPath = "\General\__ISHRemote"`, language LOV +values `$ishLng='VLANGUAGEEN'` / `$ishLngLabel='en'`, `$ishLngTarget1='VLANGUAGEES'` / `'es'`, +`$ishResolution='VRESLOW'`, status `$ishStatusDraft`/`$ishStatusReleased`, `$ishLovId='DLANGUAGE'`, +`$ishEventTypeToPurge`, etc. + +It then dot-sources the git-ignored +**`ISHRemote.PesterSetup.Debug.ps1`** (if present) as the local source of truth, to **point at your +tenant and re-map those list-of-values to match that database**: + +```powershell +$baseUrl = 'https://your-tenant.example.com' +$webServicesBaseUrl = "$baseUrl/ISHWS/" # MUST end with a trailing slash +$ishUserName = 'admin'; $ishPassword = 'admin' +$amClientId = '...' ; $amClientSecret = '...' +# Re-map LOV/language codes to this tenant's configuration: +$ishLngLabel='en-us'; $ishLngTarget1='VLANGUAGEESES'; $ishLngTarget1Label='es-es' +$ishLngTarget2='VLANGUAGEDEDE'; $ishLngTarget2Label='de-de'; $ishLngCombination='en-us' +$ishEventTypeToPurge='TESTBACKGROUNDTASK' +``` + +Why this matters: language/LOV element names and labels differ per database. A default DITA tenant +uses short codes (`VLANGUAGEEN` / `en`), while RWS demo tenants (`*.sdlproducts.com`) use +`VLANGUAGEESES` / `es-es` style codes. Tests reference the **variables**, never hard-coded language +codes, so a correct Debug override is what makes the suite green on a given tenant. When you add a +test that needs a new piece of tenant data, add a defaulted variable to `ISHRemote.PesterSetup.ps1` +(so CI/other tenants can override it) rather than hard-coding a value. **Never commit +`ISHRemote.PesterSetup.Debug.ps1`** — it holds credentials. + +## Build test data per test, clean it up per test +Each file is responsible for **creating its own data and removing all of it**, leaving the tenant in +its original state: +- Build inside `BeforeAll` (per `Describe`/`Context`) or inline in the `It`. Folder tests create a + subfolder **named after the cmdlet** under the shared root: + `Add-IshFolder -ParentFolderId $folderIdTestRootOriginal -FolderName $cmdletName ...`. +- Clean up in the top-level `AfterAll`, scoped to this file only. Canonical patterns: + - Folders: `Remove-IshFolder -FolderPath (Join-Path $folderTestRootPath $cmdletName) -Recurse`. + - Users: `Find-IshUser -MetadataFilter (Set-IshMetadataFilterField -Name USERNAME -FilterOperator like -Value "$cmdletName%") | Remove-IshUser`. + - Wrap deletes in `try { ... } catch { }` so cleanup never fails the run. +- Name created objects with `$cmdletName` + a timestamp (`Get-Date -Format "yyyyMMddHHmmssfff"`) so + filter-based cleanup is reliable and names stay unique. When creating many objects fast, add + `Start-Sleep -Milliseconds 1000` to dodge the second-resolution CARD unique-name constraint + (`Cannot insert duplicate key row ... 'CARD_NAME_I1'`). + +## Do NOT assume parallel execution +The tests share mutable server state, so they are effectively **serial**: +- **Hot spot = the folder named after the cmdlet.** Every file works under the same + `\General\__ISHRemote` root and creates `\General\__ISHRemote\<Cmdlet>`. Two files for the same/ + overlapping cmdlet folder, or a `-Recurse` cleanup, will collide if run concurrently. +- **Settings tests mutate global configuration** (field setup / metadata-bound fields, + `FISHEXTENSIONCONFIG`, output formats, LOV values, background-task/event setup). Those changes are + server-wide, so they **interfere with content-object tests and cause race-condition flakiness** if + run at the same time. +- Because of this, [ISHRemote.PesterSetup.Run.ps1](../../Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.PesterSetup.Run.ps1) + forces `*IshOutputFormat*`, `*IshLovValue*`, `*IshBackgroundTask*`, `*IshEvent*` and `*IshUserRole*` + to run **serially**, only the rest in parallel jobs. CI runs the whole suite serially. +- Keep every test **self-contained and order-independent**: create what you need, assert, delete it, + and don't rely on objects another file produced. diff --git a/.github/instructions/source-codereview-csharp.instructions.md b/.github/instructions/source-codereview-csharp.instructions.md new file mode 100644 index 0000000..12f4e0f --- /dev/null +++ b/.github/instructions/source-codereview-csharp.instructions.md @@ -0,0 +1,136 @@ +````instructions +--- +applyTo: "Source/ISHRemote/Trisoft.ISHRemote/**/*.cs" +description: "Reviewer's checklist for ISHRemote C# pull requests: build hygiene, file/type naming, session lifecycle, pipeline shape, help generation, exception handling, and backward compatibility." +--- + +# ISHRemote C# Code Review Checklist + +Use this when reviewing a PR that touches `.cs` files. Each item is a binary pass/flag. +Authoring detail for each topic lives in the companion instruction files injected per path: +[`source-cmdlets--csharp.instructions.md`](source-cmdlets--csharp.instructions.md), +[`source-objects--csharp.instructions.md`](source-objects--csharp.instructions.md), +[`source-connection--csharp.instructions.md`](source-connection--csharp.instructions.md), +[`source-extensionmethods--csharp.instructions.md`](source-extensionmethods--csharp.instructions.md). + +## 1. Build hygiene + +- [ ] Release build passes with **zero warnings** — `TreatWarningsAsErrors=true` is set in + `Directory.Build.props`; any new warning is a Release build failure. +- [ ] `[OutputType(typeof(IshX))]` uses `typeof`, **not** `nameof` — `nameof` silently skips help + generation at `net48` build time (XmlDoc2CmdletDoc limitation). +- [ ] Every property decorated `[Parameter]` has a **getter** — a setter-only property crashes + help generation during the `net48` MSBuild targets. +- [ ] Apache 2.0 license header is present and verbatim (same text as siblings). + +## 2. File, class, and type naming + +- [ ] File name = class name = `VerbIshNoun` (no hyphen): `AddIshUser.cs` → class `AddIshUser` → + `[Cmdlet(VerbsCommon.Add, "IshUser", ...)]`. All three must match exactly. +- [ ] Verb uses the .NET constant (`VerbsCommon.Get`, `VerbsLifecycle.Stop`, etc.) — never a raw + string literal. +- [ ] Class is `sealed` and inherits the **per-domain base class** (`UserCmdlet`, `FolderCmdlet`, + …); a new cmdlet in a domain that has no base class needs a new base class matching siblings. +- [ ] Parameter names are **singular**: `IshObject`, `FilePath`, `Id` — never `IshObjects`, + `FilePaths`, `Ids`. +- [ ] Parameter set names follow the singular `<Concept>Group` pattern; the canonical pair is + `ParameterGroup` / `IshObjectGroup`. Any new set name must be discussed with the implementer. +- [ ] `DefaultParameterSetName` is set whenever more than one parameter set exists. + +## 3. Session resolution + +- [ ] Session is resolved in **`BeginProcessing`**, not in `ProcessRecord`, using this exact + three-fallback chain (copied verbatim from a sibling such as `AddIshUser.cs`): + ```csharp + if (IshSession == null) { IshSession = (IshSession)SessionState.PSVariable.GetValue(ISHRemoteSessionStateIshSession); } + if (IshSession == null) { IshSession = (IshSession)SessionState.PSVariable.GetValue(ISHRemoteSessionStateGlobalIshSession); } + if (IshSession == null) { throw new ArgumentException(ISHRemoteSessionStateIshSessionException); } + ``` + +## 4. Pipeline shape and throughput + +- [ ] Write cmdlets (`Add/Set/Move/Remove/Publish/Stop`) have `SupportsShouldProcess = true` and + wrap the server call in `if (ShouldProcess(target)) { … }`. +- [ ] After a write the cmdlet **retrieves and returns** typed `Ish*` objects shaped by + `DefaultRequestedMetadata` / `PipelineObjectPreference` — raw service responses are never emitted + to the pipeline. +- [ ] Pipeline input is **accumulated in `ProcessRecord`** into a private field and flushed as + grouped/batch calls in `EndProcessing` where a batch API exists; per-item calls are only + acceptable when the server exposes no batch endpoint. +- [ ] Batching uses `DivideListInBatches<T>(list, IshSession.MetadataBatchSize)` (or `BatchSize` + for object cards) — no unbounded single server calls. +- [ ] Metadata is converted via `IshSession.IshTypeFieldSetup.ToIshMetadataFields` / + `ToIshRequestedMetadataFields`; hand-built metadata XML is a bug. + +## 5. `Get-Help` / triple-slash documentation + +- [ ] The cmdlet class XML doc block contains: + - `<para type="synopsis">` — one-line summary + - `<para type="description">` — full description + - At least one `<example>` with `<code>` and an explanatory `<para>` +- [ ] Examples show **only the cmdlet itself** — no `New-IshSession` call (it leaks server URLs + and credential patterns). +- [ ] Every `[Parameter]` property has `<para type="description">` with wording consistent with + siblings for the same parameter name. + +## 6. Exception handling + +- [ ] Catch ladder is in this exact order: + `TrisoftAutomationException` → `AggregateException` → `TimeoutException` → + `CommunicationException` → `Exception`. +- [ ] Every catch calls `ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, + ErrorCategory.XXX, null))` with the correct `ErrorCategory` — no silent swallows, no plain + `throw`. +- [ ] The order is not reordered, collapsed, or partially removed without explicit sign-off from + the implementer. + +## 7. Backward compatibility and protocol + +- [ ] No public `Ish*` member or cmdlet parameter is **renamed** without both a `Set-Alias` kept + in the cmdlet and an explicit breaking-change flag in the PR description. +- [ ] New functionality prefers the **OpenAPI** path (`OpenApiWithOpenIdConnect`) where server + parity exists (15.3.0+). A SOAP-only implementation for new work needs an explicit justification + from the implementer. + +## 8. Test coverage + +- [ ] Every new cmdlet has a matching `*.Tests.ps1` in the same domain folder. +- [ ] `ISHRemote.PesterSetup.Debug.ps1` is **not** committed (it is git-ignored and contains live + credentials). +- [ ] Any new server-less test file does **not** dot-source `ISHRemote.PesterSetup.ps1` (that + file creates an `$ishSession` and requires a live server). + +## 9. Diagnostic logging density + +A `-Debug` or `-Verbose` transcript must be enough to reconstruct what happened and diagnose a +ticket without requiring reproduction on a live server. Check each log point. + +- [ ] `BeginProcessing` ends with the session-confirmation `WriteDebug`: + ```csharp + WriteDebug($"Using IshSession[{IshSession.Name}] from SessionState.{ISHRemoteSessionStateIshSession} or in turn SessionState.{ISHRemoteSessionStateGlobalIshSession}"); + ``` +- [ ] Major phases are marked with `WriteDebug`: `"Validating"` → `"Adding"` / `"Updating"` / + `"Deleting"` → `"Retrieving"`. +- [ ] Empty-collection early exits surface both skipped actions via `WriteVerbose`: + `"IshObject is empty, so nothing to update"` / `"...to retrieve"`. +- [ ] Per-item loops log the key identifier(s) and `{++current}/{total}` at `WriteDebug` level. +- [ ] Pre-call parameter summaries (IDs, metadata XML length, filter lengths) are logged at + `WriteDebug` level before the server call. +- [ ] Post-operation result counts are logged at `WriteVerbose` level just before `WriteObject`: + `"returned object count[{n}]"`. +- [ ] `TimeoutException` and `CommunicationException` catches log + `"... Message[{msg}] StackTrace[{stack}]"` via `WriteVerbose` before `ThrowTerminatingError`. +- [ ] `AggregateException` catch logs the flattened exception via `WriteWarning` before + `ThrowTerminatingError`. +- [ ] Bare `Exception` catch logs `exception.InnerException.ToString()` via `WriteWarning` when + `InnerException != null`, before `ThrowTerminatingError`. +- [ ] OpenAPI exception catch (`OpenApiISH30Exception<InfoShareProblemDetails>`) surfaces each + structured error field via `WriteWarning` before `ThrowTerminatingError`. +- [ ] **No password, client secret, bearer token, or personal data appears in any log stream** — + log `.Length` or a masked replacement (`new string('*', value.Length)`) to confirm presence + without exposure. +- [ ] No `Write*` method (`WriteDebug`, `WriteVerbose`, `WriteWarning`, `WriteProgress`, + `WriteObject`, `ThrowTerminatingError`) is called from a non-pipeline thread — all OpenAPI async + calls use `.GetAwaiter().GetResult()` and no `Task.Run()` / `ContinueWith()` lambdas contain + any stream writes. +```` diff --git a/.github/instructions/source-codereview-pester.instructions.md b/.github/instructions/source-codereview-pester.instructions.md new file mode 100644 index 0000000..d86b02d --- /dev/null +++ b/.github/instructions/source-codereview-pester.instructions.md @@ -0,0 +1,111 @@ +````instructions +--- +applyTo: "Source/ISHRemote/Trisoft.ISHRemote/**/*.Tests.ps1" +description: "Reviewer's checklist for ISHRemote Pester (*.Tests.ps1) pull requests: file anatomy, structure, data lifecycle, session handling, tenant variables, and special test types." +--- + +# ISHRemote Pester Test Code Review Checklist + +Use this when reviewing a PR that touches `*.Tests.ps1` files. Each item is a binary pass/flag. +Authoring detail lives in +[`source-cmdlets--pester.instructions.md`](source-cmdlets--pester.instructions.md). + +## 1. File anatomy + +- [ ] Top-level `BeforeAll` sets `$cmdletName` to the exact public cmdlet name (e.g. + `"Add-IshUserGroup"`) and dot-sources `ISHRemote.PesterSetup.ps1`, if the cmdlet requires `New-IshSession` creation, via the exact relative path: + ```powershell + . (Join-Path (Split-Path -Parent $PSCommandPath) "\..\..\ISHRemote.PesterSetup.ps1") + ``` +- [ ] Top-level `BeforeAll` also emits a `Write-Host` banner: + ```powershell + Write-Host ("`r`nLoading ISHRemote.PesterSetup.ps1 on PSVersion[" + $PSVersionTable.PSVersion + "] over BeforeAll-block for MyCommand[" + $cmdletName + "]...") + ``` +- [ ] Top-level `AfterAll` exists, emits a `Write-Host` cleanup banner, and deletes **all** data + created by this file — see §4. +- [ ] The file is named `XxxIshYyy.Tests.ps1` and lives next to its `XxxIshYyy.cs` (or next to + its `.ps1` for script tests). + +## 2. Describe / Context / It structure + +- [ ] Each `Describe` block is named after the **public cmdlet** (e.g. `"Add-IshUserGroup"`) and + carries one of the CRUD tags: `-Tags "Create"`, `"Read"`, `"Update"`, or `"Delete"`. +- [ ] Each `Context` is named after the **parameter set** being exercised (e.g. + `"Add-IshUserGroup ParameterGroup"`, `"Add-IshUserGroup IshObjectGroup"`). +- [ ] The first `It` in every `Context` is an invalid-session guard: + ```powershell + It "Parameter IshSession invalid" { + { Verb-IshNoun -IshSession "INVALIDISHSESSION" ... } | Should -Throw + } + ``` +- [ ] Type-assertion `It` blocks use `Should -BeExactly` against `.GetType().Name` (not + `GetType().FullName`) to confirm the correct `Ish*` type is returned: + ```powershell + $result.GetType().Name | Should -BeExactly "IshUserGroup" + ``` + +## 3. Test data naming and uniqueness + +- [ ] Every object created in a test uses a name composed of `$cmdletName` + a millisecond + timestamp to guarantee uniqueness: + ```powershell + $name = ($cmdletName + " " + (Get-Date -Format "yyyyMMddHHmmssfff") + " SuffixLabel") + ``` +- [ ] Folder-based tests create their working folder **named after `$cmdletName`** under the + shared root: + ```powershell + Add-IshFolder -ParentFolderId $folderIdTestRootOriginal -FolderName $cmdletName ... + ``` +- [ ] When many objects are created rapidly inside a loop, `Start-Sleep -Milliseconds 1000` is + used between creates to avoid the `CARD_NAME_I1` duplicate-key constraint (second-resolution + uniqueness). This is only applied when integration tests fail. + +## 4. Self-contained data lifecycle + +- [ ] All data needed for the test is **created inside `BeforeAll`** (per `Describe`/`Context`) + or inline in the `It`; no test relies on objects a different file created. +- [ ] The top-level `AfterAll` deletes **everything this file created**, using filter-based patterns + scoped to `$cmdletName`: + ```powershell + # Folders: + Remove-IshFolder -FolderPath (Join-Path $folderTestRootPath $cmdletName) -Recurse + # Users / user groups: + Find-IshUser -MetadataFilter (Set-IshMetadataFilterField -Name USERNAME -FilterOperator like -Value "$cmdletName%") | Remove-IshUser + ``` +- [ ] Every delete in `AfterAll` is wrapped in `try { ... } catch { }` so cleanup failure never + marks the suite run as failed. + +## 5. Session handling + +- [ ] Tests use `$ishSession` from the shared `ISHRemote.PesterSetup.ps1` setup; they do **not** + call `New-IshSession` themselves. +- [ ] Each `Context` exercises **both** the explicit `-IshSession $ishSession` form **and** the + implicit (session-state-resolved) form where relevant. +- [ ] `ISHRemote.PesterSetup.Debug.ps1` is **not** committed — it is git-ignored and contains live + credentials. + +## 6. Tenant variables — no hard-coded LOV values + +- [ ] Language codes, status values, LOV element names, and similar tenant-specific constants are + referenced via the **variables declared in `ISHRemote.PesterSetup.ps1`** (e.g. `$ishLng`, + `$ishLngLabel`, `$ishLngTarget1`, `$ishStatusDraft`, `$ishStatusReleased`, `$ishLovId`). +- [ ] If a test needs a new piece of tenant data not yet in `ISHRemote.PesterSetup.ps1`, a + **defaulted variable** is added to that shared file (so CI and other tenants can override it) + rather than hard-coding a value in the test. + +## 7. Special test types + +- [ ] **Server-less tests** (e.g. `TestAnonymization.Tests.ps1`) do **not** dot-source + `ISHRemote.PesterSetup.ps1` — they must be runnable without a live server. +- [ ] **PS 7+ only tests** guard with `-Skip:($PSVersionTable.PSVersion.Major -lt 7)` on the + `It` or `Context` block. +- [ ] **Script / MCP tests** that mock collaborators use `Mock -ModuleName ISHRemote <FunctionName> + { ... }` scoped to the module, not a global mock. + +## 8. Anonymization + +- [ ] No real server URLs, hostnames, IP addresses, or credentials appear in assertions, expected + strings, or fixture data — use `https://example.com/ISHWS/` as the canonical placeholder. +- [ ] Test output printed via `Write-Host` or embedded in `Should` failure messages does not + contain customer-identifiable data. +```` diff --git a/.github/instructions/source-codereview-powershell.instructions.md b/.github/instructions/source-codereview-powershell.instructions.md new file mode 100644 index 0000000..a9259cd --- /dev/null +++ b/.github/instructions/source-codereview-powershell.instructions.md @@ -0,0 +1,79 @@ +````instructions +--- +applyTo: "Source/ISHRemote/Trisoft.ISHRemote/Scripts/**/*.ps1" +description: "Reviewer's checklist for ISHRemote PowerShell advanced-function pull requests (Scripts/Public and Scripts/Private): naming, file shape, comment-based help, export registration, StrictMode compliance, PSScriptAnalyzer, and backward compatibility." +--- + +# ISHRemote PowerShell Scripts Code Review Checklist + +Use this when reviewing a PR that touches `Scripts/Public/*.ps1` or `Scripts/Private/*.ps1`. +Each item is a binary pass/flag. Authoring detail lives in +[`source-scripts--powershell.instructions.md`](source-scripts--powershell.instructions.md). + +## 1. Naming and placement + +- [ ] Function uses an **approved PowerShell verb** (`Get-Verb` list) — no invented or aliased + verbs (`PSUseApprovedVerbs` rule). +- [ ] Noun follows the `IshRemote` or `Ish` convention: public user-facing functions use + `Verb-IshRemote*` (MCP family) or `Verb-Ish*`; private helpers use the `IshAux` infix + (e.g. `Get-IshAuxSessionState`). +- [ ] **One function per file**, file name = function name + (`Start-IshRemoteMcpServer.ps1` → `function Start-IshRemoteMcpServer`). +- [ ] Public functions land in `Scripts/Public/`; internal helpers land in `Scripts/Private/`. +- [ ] **No Apache 2.0 license header** — `.ps1` files in `Scripts/` do not carry the header + (unlike the C# tree); match the neighbours. + +## 2. Advanced function shape + +- [ ] Every function declares `[CmdletBinding()]`. +- [ ] Parameters are declared in a typed `param(...)` block with `[Parameter(Mandatory)]` where + appropriate. +- [ ] Parameter names are **singular** (`-Name`, `-FilePath`, `-IshSession`) — never plural. +- [ ] `[ValidateNotNullOrEmpty()]` or equivalent validators are used on required string parameters. + +## 3. Comment-based help + +- [ ] `.SYNOPSIS` is present — one-line summary. +- [ ] `.DESCRIPTION` is present — full description. +- [ ] Every parameter has a `.PARAMETER <Name>` block that describes its purpose. +- [ ] At least one `.EXAMPLE` block is present with realistic usage. +- [ ] All URLs in examples use `https://example.com/ISHWS/` — no real server hostnames or + credentials. See §7. + +## 4. Export registration + +- [ ] Any **new `Public/` function** that users should call is added to the `-FunctionsToExport` + list in `Trisoft.ISHRemote.csproj` — without this the function loads but is invisible. +- [ ] Functions that intentionally work as side-effecting dot-source scripts (like + `Expand-ISHParameter.ps1`) are **not** added to `-FunctionsToExport`. + +## 5. StrictMode compliance + +- [ ] No use of variables that may be unassigned at runtime (`Set-StrictMode -Version Latest` + is applied by `ISHRemote.psm1` after dot-sourcing all scripts — any unassigned variable access + throws at import time). +- [ ] No implicit `$null` member access (e.g. calling a property on a variable that could be + `$null` without a null-guard). + +## 6. PSScriptAnalyzer clean + +- [ ] `Invoke-ScriptAnalyzer -Path ... -Recurse` reports **no findings** for the changed files — + CI reports but does not fail the build on analyzer warnings; still, zero findings is the bar. +- [ ] No unapproved aliases (e.g. `%`, `?`, `select`, `where`) in shipped code. +- [ ] No unused variables left behind. + +## 7. Anonymization + +- [ ] No real server URLs, hostnames, IP addresses, or credentials in help text, examples, or + test fixture strings — use `https://example.com/ISHWS/` as the canonical placeholder. +- [ ] No hard-coded local paths (e.g. `C:\TEMP\...`, `D:\GITHUB\...`) — use parameter defaults or + `$env:TEMP` / `${env:TEMP}` where a path is needed. + +## 8. Backward compatibility + +- [ ] No `Public/` function name or parameter name is **renamed or removed** without an explicit + breaking-change flag in the PR description and implementer sign-off — these are part of the + public module surface and break existing scripts. +- [ ] If a rename is unavoidable, the old name is kept as an alias (`Set-Alias OldName NewName`) + and documented in the release notes. +```` diff --git a/.github/instructions/source-connection--csharp.instructions.md b/.github/instructions/source-connection--csharp.instructions.md new file mode 100644 index 0000000..29a56c4 --- /dev/null +++ b/.github/instructions/source-connection--csharp.instructions.md @@ -0,0 +1,156 @@ +--- +applyTo: "Source/ISHRemote/Trisoft.ISHRemote/Connection/**/*.cs" +description: "Structure and intent of the ISHRemote Connection layer: a deliberately self-contained authentication + proxy stack (WS-Trust/WS-Federation, OpenIdConnect over SOAP and over REST/OpenAPI, with Client Credentials or interactive System Browser) that should be copyable to other C# projects with minimal change — and why changes must stay inside this folder." +--- + +# ISHRemote Connection Layer Conventions + +The `Connection/` folder is the **authentication + service-proxy stack** that sits underneath +`IshSession`. It discovers how a Tridion Docs server is configured, obtains the right credential +(WS-Trust SAML token, or an OpenIdConnect access/bearer token), and hands back ready-to-call SOAP +proxies and OpenAPI clients. `IshSession` (in `Objects/Public/IshSession.cs`) is the **only** +intended consumer: it builds one `HttpClient`, fills a parameters bag, news up one of the three +connection front-ends, and then calls the `GetXxx25Channel()` / `GetOpenApi…Client()` accessors. + +> **Legacy vs future (investment).** All three flavours must keep working — ISHRemote still runs on +> older InfoShare (e.g. 13.0.2, 14.0.4) over **`WcfSoapWithWsTrust`**, which is therefore +> **maintain-only / bug-fix-only** here (the product removes it in 16.0.0). The SOAP proxies are +> **deprecated by the product**: `WcfSoapWithOpenIdConnect` stays broadly useful on 15.x and remains +> in 16.0.0 but is deprecated, while **`OpenApiWithOpenIdConnect` (REST) is the future** (parity with +> OIDC-over-SOAP at 15.3.0). Keep the WS-Trust front-end healthy, but **don't pour new feature work +> into it** — see the repo-wide `.github/copilot-instructions.md` "Legacy & where to invest less". + +> For the reviewer's binary pass/flag checklist see +> [`source-codereview-csharp.instructions.md`](source-codereview-csharp.instructions.md). + +## 0. Portability is the whole point — keep changes inside this folder +These files are written to be **standalone**. Copy the folder into another C# project and, with only +a handful of adaptations (see §7), you get **all authentication flavours** against the InfoShare / +Access Management stack: +- **WS-Trust / WS-Federation (active)** SOAP — `WindowsMixed` and `UserNameMixed`. +- **OpenIdConnect on top of SOAP/WCF** — the OIDC bearer token is wrapped so it rides over WCF. +- **OpenIdConnect on top of REST/OpenAPI** — the bearer token goes on the `Authorization` header. +- Each OIDC variant works with **Client Credentials** (`ClientId` + `ClientSecret`, non-interactive) + **or** an **interactive System Browser** flow (Authorization Code + PKCE). + +**Golden rule:** make every change *within* `Connection/`. The layer is intentionally decoupled from +the cmdlets and from `IshSession`. If a change seems to require editing anything **outside** this +folder — `IshSession`, cmdlets, `Trisoft.ISHRemote.csproj`, the generated proxies — **stop and ask +the implementer first.** A leak past this boundary means the abstraction is breaking and the design +owner needs to decide. Likewise, don't pull cmdlet/business concepts *into* this folder. + +## 1. License header (mandatory, verbatim) +Every `.cs` starts with the Apache 2.0 header exactly as in the neighbouring files (historical +`Copyright (c) 2014 ... SDL Group` text — copy it, don't "modernize" it; tooling checks it, see +[Add-SDLOpenSourceHeader.ps1](../../Source/Tools/PowerShell/Add-SDLOpenSourceHeader.ps1) / +[Test-SDLOpenSourceHeader.ps1](../../Source/Tools/PowerShell/Test-SDLOpenSourceHeader.ps1)). + +## 2. Shared conventions across every file +- Namespace is always `Trisoft.ISHRemote.Connection`. +- Concrete connections, parameter bags, the tokens DTO and the bearer-credentials helpers are + `internal sealed`; the shared base is `internal abstract`. **Exceptions to know:** + `InfoShareOpenIdConnectSystemBrowser` is `public` (it implements Duende's `IBrowser`), + `InfoShareOpenIdConnectLocalHttpEndpoint` and `IshConnectionConfiguration` are `internal` (not + sealed). Don't widen visibility without a reason. +- Constructor signature for the three connection front-ends is uniform: + `(ILogger logger, HttpClient httpClient, <…ConnectionParameters> parameters)`. The `HttpClient` is + **created once by `IshSession` and reused** (TLS/SSL already initialized) — never new up your own + `HttpClient` here. +- Log through `Trisoft.ISHRemote.Interfaces.ILogger` only (`_logger.WriteDebug` / `WriteVerbose`); + no `Console`, no `Write-Host`. Keep secrets out of logs — log `ClientSecret.Length` or a masked + `new string('*', …)`, never the secret or a raw token. +- Triple-slash `///` summaries on classes and members (this project builds XML docs and treats + warnings as errors in Release). +- URL hygiene matches the existing setters: normalize to a trailing `/`, and for OpenAPI strip the + `OWcf`/`OCoreWcf` segment off `InfoShareWSUrl` before composing the `…/api` base. +- `IgnoreSslPolicyErrors` is honoured everywhere for self-signed dev servers; preserve that hook. + +## 3. Multi-targeting: `#if` pragmas are load-bearing +The module ships `net48;net6.0;net10.0` and the WCF surface differs between .NET Framework and +CoreCLR, so the SOAP files are split with `#if NET48 / #else / #endif` (plus `#if NET6_0_OR_GREATER` +and `#if NET10_0_OR_GREATER` for narrower cases). Both arms must compile **and behave the same**: +- WS-Trust binding: `WS2007FederationHttpBinding` + manual `WSTrustChannel` on `net48` vs + `WSFederationHttpBinding` (`System.ServiceModel.Federation`) on `net6.0+`. +- OIDC-over-SOAP token attach: `CreateChannelWithIssuedToken(WrapJwt(...))` on `net48` vs adding + `BearerCredentials` endpoint behaviour on `net6.0+`. +- `InfoShareWcfSoapBearerCredentials.cs` is **entirely** `#if NET6_0_OR_GREATER` (the SAML2 wrapper + uses APIs absent from `net48`). `net48` reaches the same result through its own code path. +- `net48` also needs the catch-all `ServerCertificateCustomValidationCallback`/`BackchannelHandler` + for `/.well-known/openid-configuration` discovery. Keep these platform shims — don't "simplify" + one arm away. If you touch one arm, update the other to match. + +## 4. What each file is (and where to make a given change) +**Discovery** +- `IshConnectionConfiguration.cs` — parses `/ISHWS/connectionconfiguration.xml` (and the 15+ + `/ISHWS/owcf/…` variant) into `SoftwareVersion`, `ApplicationName`, `InfoShareWSUrl`, + `AuthenticationType`, `IssuerUrl`. This drives protocol/flavour selection upstream. + +**State (POCOs — no behaviour)** +- `InfoShareWcfSoapWithWsTrustConnectionParameters.cs` — inputs for the WS-Trust SOAP path + (`NetworkCredential`, URLs, timeouts, `IgnoreSslPolicyErrors`). +- `InfoShareOpenIdConnectConnectionParameters.cs` — inputs **shared** by *both* OIDC connections + (SOAP and OpenAPI): `ClientAppId`, `Scope`, `RedirectUri`, `ClientId`/`ClientSecret`, `Tokens`, + timeouts incl. `SystemBrowserTimeout`. Adding an OIDC knob? It belongs here so both front-ends see + it. +- `InfoShareOpenIdConnectTokens.cs` — small DTO holding `AccessToken`/`IdentityToken`/`RefreshToken` + + `AccessTokenExpiration`. + +**OIDC token engine** +- `InfoShareOpenIdConnectConnectionBase.cs` — the single source of token logic shared by the two + OIDC connections: `GetTokensOverClientCredentialsAsync` (Client Credentials), + `GetTokensOverSystemBrowserAsync` (interactive Authorization Code + PKCE via Duende `OidcClient`), + `RefreshTokensAsync`, and `GetAccessToken()` which transparently refreshes using the + `RefreshBeforeExpiration` skew (default 3 min) and decides refresh-vs-client-credentials by whether + `ClientId`/`ClientSecret` are set. **Put shared token behaviour here**, not in a front-end. +- `InfoShareOpenIdConnectSystemBrowser.cs` — `IBrowser` implementation; launches the OS default + browser cross-platform (Windows/Linux/macOS) for the interactive flow. +- `InfoShareOpenIdConnectLocalHttpEndpoint.cs` — the `127.0.0.1` `HttpListener` that catches the + OIDC redirect callback (kept alive briefly to render the "you are signed in" page). +- `InfoShareWcfSoapBearerCredentials.cs` (`net6.0+` only) — wraps the OIDC JWT into a self-signed + SAML2 token (`BearerCredentials` + token manager/provider/serializer) so a bearer token passes + cleanly over WCF. + +**Connection front-ends (what `IshSession` news up — one per protocol)** +- `InfoShareWcfSoapWithWsTrustConnection.cs` — dynamic (no `app.config`) WCF proxy generation + secured by WS-Trust; exposes `GetXxx25Channel()` accessors and lazy issued token. Does **not** + inherit the OIDC base. +- `InfoShareWcfSoapWithOpenIdConnectConnection.cs` — same dynamic WCF proxies, but secured by an + OIDC bearer token (wrapped per §3); **inherits** `InfoShareOpenIdConnectConnectionBase`. +- `InfoShareOpenApiWithOpenIdConnectConnection.cs` — wraps the NSwag OpenAPI clients + (`OpenApiISH30Client`, `OpenApiAM10Client`), setting the `Bearer` header from `GetAccessToken()`; + **inherits** the OIDC base; `IDisposable`. + +**Diagram** +- `__ConnectionClassDiagram.cd` — Visual Studio class diagram. **Keep it in sync** when you add, + remove, or rename a class in this folder. + +## 5. Adding / changing a connection class — checklist +- New auth *flavour*? Add a front-end that follows the `(ILogger, HttpClient, …Parameters)` ctor + shape and (for OIDC) inherits `InfoShareOpenIdConnectConnectionBase`; reuse the existing parameters + bag rather than inventing a parallel one. +- New SOAP service proxy? Follow the established `private … _xxxClient;` field + + `GetXxx25Channel()` accessor + `const string Xxx25 = "Xxx25";` naming, and add it to **both** + `#if` arms where the WS-Trust/OIDC SOAP files diverge. +- Shared token/refresh behaviour goes in the **base**, not duplicated across front-ends. +- Update `__ConnectionClassDiagram.cd` and keep triple-slash docs warning-clean. + +## 6. Don't +- Don't edit anything outside `Connection/` to make a connection change work — ask the implementer + (see §0). +- Don't create or dispose your own `HttpClient`; use the injected one. +- Don't drop or one-sidedly diverge a `#if NET48` / `#else` arm. +- Don't log tokens, passwords, or client secrets. +- Don't hand-edit the generated SOAP `*25ServiceReference` proxies or the NSwag OpenAPI clients from + here. + +## 7. Reusing this folder in another C# project (the standalone contract) +When you copy `Connection/` out, these are the only external seams to satisfy — by design: +- `Trisoft.ISHRemote.Interfaces.ILogger` — swap for your own logging abstraction. +- `Trisoft.ISHRemote.Exceptions` — used by `IshConnectionConfiguration`; bring it or replace it. +- The generated SOAP `*25ServiceReference` proxies (the WCF front-ends new these up) — point at your + own generated service references. +- The NSwag OpenAPI clients `Trisoft.ISHRemote.OpenApiISH30` / `…OpenApiAM10`. +- NuGet: `Duende.IdentityModel` + `Duende.IdentityModel.OidcClient`, and `Newtonsoft.Json`. + +If you keep those seams thin, the WS-Trust, OIDC-over-SOAP and OIDC-over-OpenAPI flavours — with +Client Credentials or interactive browser auth — all come across intact. diff --git a/.github/instructions/source-extensionmethods--csharp.instructions.md b/.github/instructions/source-extensionmethods--csharp.instructions.md new file mode 100644 index 0000000..5ec297f --- /dev/null +++ b/.github/instructions/source-extensionmethods--csharp.instructions.md @@ -0,0 +1,115 @@ +--- +applyTo: "Source/ISHRemote/Trisoft.ISHRemote/ExtensionMethods/**/*.cs" +description: "Structure and intent of the ISHRemote ExtensionMethods layer: an experimental, additive bridge that augments the SOAP-inspired public Objects (the integration contract used by PS1 scripts and Pester tests) so they can interpret the richer, more strongly-typed JSON OpenAPI object model while staying compliant with client-side ISHRemote enumerations." +--- + +# ISHRemote `ExtensionMethods/` Conventions + +> **Status: experimental and intentionally incomplete.** This folder is a young bridging layer. It +> has `TODO`s, open `// [Question]` notes, and even a fully commented-out file. That is expected — +> when something here is ambiguous or missing, **ask the implementer** rather than inventing a +> contract. These instructions will be refined as the layer matures. + +> For the reviewer’s binary pass/flag checklist see +> [`source-codereview-csharp.instructions.md`](source-codereview-csharp.instructions.md). + +## 0. Why this folder exists (the big picture) +ISHRemote's **public object model lives in `Objects/`** (e.g. `Objects/Public/IshObject.cs`, +`IshField.cs`, and the `IshFields` collection). Those POCO-like classes are **part of the public +contract**: custom `.ps1` integration scripts and the Pester tests consume them directly, so their +shape must stay stable. That model is **SOAP-inspired** — it mirrors the `<ishobjects>`/`<ishfield>` +hierarchy of the WCF SOAP 2.5 API, so values are largely **stringly-typed** (a field value is text, +multi-value is one string split on `IshSession.Separator`). + +The later **OpenAPI (ISH30 / AM10) model is richer**: it is JSON-based and **strongly typed** — +distinct `SetStringFieldValue` / `SetLovFieldValue` / `SetCardFieldValue` / `SetDateTimeFieldValue` / +`SetNumberFieldValue` (plus `Multi…` variants), typed `Level`, `FieldGroup`, `StatusFilter`, etc. + +`ExtensionMethods/` is the **adapter** between those two worlds. Its job is twofold: +1. **Interpret the typed JSON/OpenAPI shapes** and project them onto (or from) the existing public + `Ish*` objects, so the rest of ISHRemote keeps speaking the stable SOAP-style model. +2. **Stay compliant with client-side ISHRemote enumerations** (`Objects/Enumerations.cs`) by mapping + each OpenAPI enum to/from its ISHRemote counterpart in exactly one place. + +## 1. License header (mandatory, verbatim) +Every `.cs` starts with the Apache 2.0 header exactly as in the neighbouring files (historical +`Copyright (c) 2014 ... SDL Group` text — copy it, don't "modernize" it; tooling checks it, see +[Add-SDLOpenSourceHeader.ps1](../../Source/Tools/PowerShell/Add-SDLOpenSourceHeader.ps1) / +[Test-SDLOpenSourceHeader.ps1](../../Source/Tools/PowerShell/Test-SDLOpenSourceHeader.ps1)). + +## 2. Shape of an extension file +- Namespace is always `Trisoft.ISHRemote.ExtensionMethods`. +- One **`internal static` class** per file holding only **`internal static` extension methods** (the + first parameter is `this <Type>`). Nothing here is `public` — this is an internal helper layer, not + part of the public contract (the public contract is `Objects/`). +- **File naming = the subject type + `Extensions`**, named after whichever side the method *extends*: + - `OpenApiISH30…Extensions.cs` when extending an OpenAPI type + ([OpenApiISH30EnumerationsExtensions.cs](../../Source/ISHRemote/Trisoft.ISHRemote/ExtensionMethods/OpenApiISH30EnumerationsExtensions.cs), + [OpenApiISH30FieldValueExtensions.cs](../../Source/ISHRemote/Trisoft.ISHRemote/ExtensionMethods/OpenApiISH30FieldValueExtensions.cs)). + - `Ish…Extensions.cs` when extending an ISHRemote `Objects/` type + ([IshFieldsExtensions.cs](../../Source/ISHRemote/Trisoft.ISHRemote/ExtensionMethods/IshFieldsExtensions.cs)). + - (The class inside may be `…ExtensionMethods` or `…Extensions` — both exist today; prefer matching + the file name for new files.) +- Disambiguate the two object worlds explicitly in code: qualify with `OpenApiISH30.IshField` vs + `Objects.Public.IshField`, `OpenApiISH30.Level` vs `Enumerations.Level`, etc. Both define + same-named types, so never rely on a bare `using` to pick the right one. + +## 3. Method naming = direction of conversion (keep it predictable) +Every method is a converter and its name states the **target**: +- **ISHRemote → OpenAPI:** `ToOpenApiISH30<Thing>()` — e.g. + `ToOpenApiISH30Level`, `ToOpenApiISH30FieldGroup`, `ToOpenApiISH30StatusFilter`, + `ToOpenApiISH30SetFieldValues`, `ToOpenApiISH30FilterFieldValues`, `ToOpenApiISH30RequestedFields`. +- **OpenAPI → ISHRemote:** `ToISH<Thing>()` — e.g. `ToISHFieldLevel`. +Reuse the existing verb/shape for a new mapping instead of coining a new pattern. + +## 4. The enumeration-compliance rule (single source of truth) +Each OpenAPI ↔ ISHRemote enum mapping is a `switch` that lives **once** in +[OpenApiISH30EnumerationsExtensions.cs](../../Source/ISHRemote/Trisoft.ISHRemote/ExtensionMethods/OpenApiISH30EnumerationsExtensions.cs) +(or alongside the field logic for `Level` in `IshFieldsExtensions`). When you add an enum value: +- Handle **every** case; for a `…ToISH…` mapping that hits a genuinely unmappable OpenAPI value + (`Object`, `Compute`), **throw `ArgumentException`** with the `"… [{value}] was unexpected."` + wording already used — don't silently default. +- For `…ToOpenApiISH30…` mappings a `default:` fallback (e.g. to `…Descriptive`/`…None`) is the + established style; keep unsupported cases marked with a `// TODO [Could] API30 enumerations` note + rather than guessing a wrong target. +- Never duplicate a mapping in a second file — call the existing extension. + +## 5. Bridging field values correctly (the hard part) +Converting `IshFields` (stringly-typed) to typed OpenAPI `SetFieldValue`s is **driven by the field +setup, not by guessing**. Follow the pattern in +[IshFieldsExtensions.cs](../../Source/ISHRemote/Trisoft.ISHRemote/ExtensionMethods/IshFieldsExtensions.cs): +- Look up the field's `IshTypeFieldDefinition` (via `ishSession.IshTypeFieldDefinition`, matched on + `Name` + `Level`); skip fields that have no definition. +- Branch on `ishTypeFieldDefinition.DataType` (`DateTime` / `ISHLov` / `ISHType` / `String` / + `LongText` / `ISHMetadataBinding` / `Number`) **and** on `IsMultiValue` to pick the right + `Set…FieldValue` vs `SetMulti…FieldValue`. +- For multi-value, split the string on `ishSession.Separator` (the same separator the SOAP model + uses) before projecting each part. +- For `ISHType` (card reference) fields, map `ReferenceType` to the correct `Set…` object + (`SetUser`, `SetUserGroup`, `SetFolder`, `SetElectronicDocumentType`, `SetDocumentObject`, …) — see + `GetSetBaseObjectFromReferenceType`. +- The reverse direction (OpenAPI `FieldValue` → `IshFields`) re-emits the SOAP-style `Value`/`Id`(/ + `Element`) note-fields and joins multi-values back with `IshSession.Separator` (see how + `ToISHFieldLevel` is consumed in [Objects/IshFields.cs](../../Source/ISHRemote/Trisoft.ISHRemote/Objects/IshFields.cs)). + +## 6. Don't break the public `Objects/` contract +- This layer is **additive**: it augments/translates the public `Ish*` objects. Do **not** change the + shape, naming, or semantics of anything in `Objects/Public/` to make a conversion easier — that + model is consumed by external scripts and the Pester suite. If a conversion seems to *need* a public + change, stop and ask the implementer. +- Keep the SOAP-style invariants the public model relies on (string values, `IshSession.Separator` + for multi-value, `Value`/`Id`/`Element` note-field triplets). +- Don't hand-edit the generated OpenAPI clients (`Trisoft.ISHRemote.OpenApiISH30` / + `…OpenApiAM10`); adapt around them here. + +## 7. Working with the experimental nature +- `TODO`, `// [Question]`, and large commented-out blocks (e.g. the `#115` attempt in + [OpenApiISH30FieldValueExtensions.cs](../../Source/ISHRemote/Trisoft.ISHRemote/ExtensionMethods/OpenApiISH30FieldValueExtensions.cs)) + are deliberate breadcrumbs — leave them unless you are the one completing that path, and prefer + extending them over deleting context. +- Consumers today are mostly the **side-by-side OpenAPI code paths** (e.g. + [AddIshFolder.cs](../../Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Folder/AddIshFolder.cs)); a method + may exist before it has a live caller. That is fine for this layer — but call it out so the + implementer can decide whether to wire it in. +- Keep the build warning-clean (Release treats warnings as errors). New mappings need accurate + triple-slash summaries where the siblings have them. diff --git a/.github/instructions/source-objects--csharp.instructions.md b/.github/instructions/source-objects--csharp.instructions.md new file mode 100644 index 0000000..212cd86 --- /dev/null +++ b/.github/instructions/source-objects--csharp.instructions.md @@ -0,0 +1,115 @@ +--- +applyTo: "Source/ISHRemote/Trisoft.ISHRemote/Objects/**/*.cs" +description: "Structure and intent of the ISHRemote Objects layer: the public Ish* models are the SOAP-inspired integration contract consumed by PS1 scripts and Pester tests (backward-compatible, implementer-approval to change, tightly correlated to ISHRemote.Format.ps1xml), while the internal container objects merely group public objects off the pipeline." +--- + +# ISHRemote `Objects/` Conventions + +`Objects/` holds ISHRemote's **data model**. It has two tiers with very different rules: +- **`Objects/Public/` (and the `public` types in `Objects/` root) = the integration CONTRACT.** + These `Ish*` classes (and `Enumerations`, `IshBaseObject`) are what flows on the PowerShell + pipeline and what custom `.ps1` scripts and the **Pester tests** bind to by property name. They are + **backward-compatibility surface** — just like a cmdlet's parameter names and parameter sets. +- **`Objects/` root `internal` types = containers.** `IshObjects`, `IshFolders`, `IshLovValues`, + `IshBackgroundTasks`, `IshBaselineItems`, `IshEvents`, `IshFeatures`, `IshSearchResults` are + plumbing that **groups** public objects parsed from a service response. They are **not** meant to + reach the pipeline. + +> For the reviewer’s binary pass/flag checklist see +> [`source-codereview-csharp.instructions.md`](source-codereview-csharp.instructions.md). + +## 0. The public objects are a compatibility contract (get approval to change) +A `public` type/member here is consumed by code we don't control (customer scripts) and by the test +suite. Therefore: +- **Adding** a property/type is generally safe (additive). +- **Renaming, removing, retyping, or reordering** a public member is a **breaking change** and + requires **implementer approval** — exactly the same bar as renaming a cmdlet parameter or a + parameter set. Don't do it casually to "tidy up". +- Be aware that `public` reaches beyond `Objects/Public/`: `Enumerations` + ([Objects/Enumerations.cs](../../Source/ISHRemote/Trisoft.ISHRemote/Objects/Enumerations.cs)) and + `IshBaseObject` ([Objects/ISHBaseObject.cs](../../Source/ISHRemote/Trisoft.ISHRemote/Objects/ISHBaseObject.cs)) + live in the root but are public contract too. Keep enum **names and integer values** stable. +- If a change seems to require breaking the public shape, **stop and ask the implementer** before + touching it. + +## 1. License header (mandatory, verbatim) +Every `.cs` starts with the Apache 2.0 header exactly as in the neighbouring files (historical +`Copyright (c) 2014 ... SDL Group` text — copy it, don't "modernize" it; tooling checks it, see +[Add-SDLOpenSourceHeader.ps1](../../Source/Tools/PowerShell/Add-SDLOpenSourceHeader.ps1) / +[Test-SDLOpenSourceHeader.ps1](../../Source/Tools/PowerShell/Test-SDLOpenSourceHeader.ps1)). + +## 2. SOAP-inspired shape (why the model looks like it does) +The public model mirrors the WCF SOAP 2.5 `<ishobjects>` hierarchy, so most files carry a sample XML +snippet in a comment and a `public Ish*(XmlElement xml…)` constructor that parses it: +- `IshObject` ⇐ `<ishobject ishref=… ishtype=… ishlogicalref=…>` with nested `<ishfields>` and + optional `<ishdata>` blob. +- `IshFolder` ⇐ `<ishfolder ishfolderref=… ishfoldertype=…>`, `IshLovValue` ⇐ `<ishlovvalue …>`, etc. +Values are **stringly-typed** and multi-value fields are a single string joined by +`IshSession.Separator`. Newer types may **also** offer an OpenAPI constructor (e.g. +`IshFolder(OpenApiISH30.Folder oFolder, string separator)`) — keep both construction paths producing +the **same** public shape (see the ExtensionMethods layer for the JSON↔model bridge). + +## 3. Public vs container — and why containers stay off the pipeline +- A **container** (`internal class IshObjects`, `IshLovValues`, …) wraps a `List<IshPublicType>` + parsed from one XML/JSON response and exposes accessors like `.Objects`, `.ObjectList`, `.Ids`. +- **PowerShell pipelines prefer an array of public objects over a single container object.** A lone + container forces the user to reach inside it; an enumerated array of `Ish*` objects "just works" + with `Where-Object`, `Select-Object`, `Format-Table`, `Out-GridView`. So the pattern is: cmdlet + builds a container internally, then writes **`container.Objects` enumerated** to the pipeline — + never the container itself. +- Keep containers `internal`; don't add pipeline-facing features to them. If you need something on + the pipeline, it belongs on the **public** element type. + +## 4. Pipeline emission & PSNoteProperty enrichment +Cmdlets don't `WriteObject` these raw; they go through the helpers in +[Cmdlets/TrisoftCmdlet.cs](../../Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/TrisoftCmdlet.cs) +(`WriteObject(IshSession, ISHType[], IshBaseObject(s), enumerateCollection)`): +- With `IshSession.PipelineObjectPreference = PSObjectNoteProperty` (default), each object is wrapped + in a `PSObject` and **one `PSNoteProperty` per `IshField`** is added (names/formatting from + `IshSession.NameHelper.GetPSNoteProperty`, e.g. dates normalized to sortable ISO‑8601). These + note-properties are what surface as columns in `Select-Object *` / `Out-GridView`. +- With `PipelineObjectPreference = Off`, the bare `Ish*` objects are written (still enumerated). +- This is exactly why `IshBaseObject` exists: it exposes the `IshFields` the wrapper iterates. New + pipeline types should derive from `IshBaseObject` (directly or via `IshObject`) so they enrich + consistently. + +## 5. Strong correlation with `ISHRemote.Format.ps1xml` +The human-readable table rendering of every public object is defined in +[ISHRemote.Format.ps1xml](../../Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.Format.ps1xml), and it +is **tightly coupled** to these classes: +- Each public type has a `<View>` selected by its **full type name** + (`Trisoft.ISHRemote.Objects.Public.IshObject`), whose `<TableControl>` lists the exact + **`<PropertyName>`s**, their **column order**, **labels**, and **`<Width>`s**. +- Therefore a public **property name must keep matching** the `ps1xml`. If you rename/remove a + property, add a property meant to show by default, or change what a column should display, **update + `ISHRemote.Format.ps1xml` in the same change** (and keep widths sensible). +- This is also why **typed specializations exist**: e.g. `IshDocumentObj : IshObject` adds typed ref + getters (`VersionRef`, `LngRef`, a `new ObjectRef`) largely "to allow *.Format.ps1xml to do magic" + — a distinct type gives a distinct default view. `IshObjectFactory` returns the **most specific** + type so the right view is selected. +- When you add a brand-new public pipeline type, add a matching `<View>` (copy a sibling view and + adjust columns/widths). + +## 6. Construction & specialization patterns to follow +- Provide the established constructors: an **`XmlElement`** (SOAP) one and, where relevant, an + explicit-fields one and/or an **OpenAPI model** one — all yielding the same public shape. +- Specialize by **inheriting `IshObject`** and adding strongly-typed getters; use `new` to refine a + base member only when the SOAP-style base would be ambiguous (as `IshDocumentObj.ObjectRef` does). +- Register new specializations in + [IshObjectFactory.cs](../../Source/ISHRemote/Trisoft.ISHRemote/Objects/IshObjectFactory.cs) so the + right concrete type is built from a generic `<ishobject>`. +- Keep `///` triple-slash docs with the `<para type="description">…</para>` style used by siblings + (these flow into help and the build is warning-clean in Release). +- Keep the class diagram + [_ObjectsDiagram.cd](../../Source/ISHRemote/Trisoft.ISHRemote/Objects/_ObjectsDiagram.cd) in sync + when you add/rename/remove a type. + +## 7. Don't +- Don't rename/remove/retype/reorder a **public** member without implementer approval — it breaks + scripts, the Pester suite, and `ISHRemote.Format.ps1xml` (see §0, §5). +- Don't make a container `public` or write a container straight to the pipeline — emit its + `.Objects` array enumerated instead (see §3). +- Don't change `Enumerations` value names or numbers (serialized/contract); add new members instead. +- Don't add a public default-visible property without updating the matching `Format.ps1xml` view. +- Don't break the SOAP-style invariants the model relies on (string values, + `IshSession.Separator` for multi-value, `Value`/`Id`/`Element` note-field triplets). diff --git a/.github/instructions/source-samples--mixed.instructions.md b/.github/instructions/source-samples--mixed.instructions.md new file mode 100644 index 0000000..3ff1777 --- /dev/null +++ b/.github/instructions/source-samples--mixed.instructions.md @@ -0,0 +1,93 @@ +--- +applyTo: "Source/ISHRemote/Trisoft.ISHRemote/Samples/**/*" +description: "Structure and intent of the ISHRemote Samples folder: historical, inspirational Sample.*.ps1 automation scripts and self-contained data/debugging sets (e.g. Data-GeneralizeDitaXml) that double as fixtures for Pester tests — all kept fully anonymized (no customer URLs or secrets), like the ReleaseNotes." +--- + +# ISHRemote `Samples/` Conventions + +> **Status: lightly used these days.** `Samples/` is reference material, not shipped product code. +> It is used less now — mostly because few cmdlets need large file-system test data — but it remains +> the right home for **inspirational usage scripts** and **self-contained data sets** when they are +> needed. Don't expect every sample to use the newest cmdlet syntax; many are intentionally +> historical. + +## 0. What this folder is +Two kinds of content live here, with different purposes: +1. **`Sample.*.ps1` automation scripts** — end-to-end examples showing *how the ISHRemote library can + be used* for real business automation (provisioning, legacy correction, publishing). Somewhat + legacy, but still inspirational. +2. **Data subfolders** (e.g. + [Data-GeneralizeDitaXml/](../../Source/ISHRemote/Trisoft.ISHRemote/Samples/Data-GeneralizeDitaXml), + [Data-DocumentObj/](../../Source/ISHRemote/Trisoft.ISHRemote/Samples/Data-DocumentObj), + [Publish-PublicationOutputs/](../../Source/ISHRemote/Trisoft.ISHRemote/Samples/Publish-PublicationOutputs)) + — **self-contained example + debugging sets**: catalogs, DTDs, sample DITA/XML and a driver + script, complete enough to run and hand-explain. + +## 1. Golden rule — anonymize everything (no customer URLs, no secrets) +Exactly like the `Doc/` ReleaseNotes, **everything in `Samples/` is public and must be anonymized.** +Use the placeholders already established across the folder: +- **URLs:** `https://example.com/ISHWS/` or `https://example.com/InfoShareWS/` — never a real + customer/host URL. +- **Credentials:** empty or generic placeholders (`$trisoftUserName = ''`, `username` / `password`). + Never a real username, password, client id/secret, or token. +- **Domains / emails / users:** `exampledomain`, `@example.com`, `user1`…`user5`. +- **GUIDs, file names, paths:** keep them obviously fake (`GUID-EB2E82F1-…`, `C:\PublishedDocs`). +If you add or edit a sample, scrub it before committing — treat a leaked URL/secret as a bug. + +## 2. `Sample.*.ps1` script conventions +Follow the shape of the existing scripts +([Sample.Automate.ActiveDirectory.ps1](../../Source/ISHRemote/Trisoft.ISHRemote/Samples/Sample.Automate.ActiveDirectory.ps1), +[Sample.Automate.LegacyCorrection.ps1](../../Source/ISHRemote/Trisoft.ISHRemote/Samples/Sample.Automate.LegacyCorrection.ps1), +[Sample.Automate.ProvisionUsers.ps1](../../Source/ISHRemote/Trisoft.ISHRemote/Samples/Sample.Automate.ProvisionUsers.ps1)): +- **Naming:** `Sample.<Theme>.<Topic>.ps1` (e.g. `Sample.Automate.ProvisionUsers.ps1`). A sample that + needs its own data gets a sibling **subfolder** holding the script + its files (see + `Publish-PublicationOutputs/`). +- **Self-documenting top:** either a comment-based help block (`.SYNOPSIS` / `.DESCRIPTION` / + `.PARAMETER`) or a clear `#` header explaining the scenario and any minimum ISHRemote version. +- **Recognisable preamble:** `Import-Module ISHRemote -DisableNameChecking`, then the + `$DebugPreference/$VerbosePreference/$WarningPreference/$ProgressPreference` block, then anonymized + variables, then a `try { New-IshSession … } …` body. +- These are **illustrative, not tested** — they may use older parameter names/positional + `New-IshSession` calls. When *adding new* samples prefer current cmdlet conventions and an existing + `$ishSession`; when touching an old one, keep it working rather than fully modernising it (and if a + rewrite seems warranted, ask the implementer). +- **No Apache license header** is used on these scripts (unlike the C# tree) — match the neighbours. + +## 3. Self-contained data subfolders (and why they aren't generated) +A folder like +[Data-GeneralizeDitaXml/](../../Source/ISHRemote/Trisoft.ISHRemote/Samples/Data-GeneralizeDitaXml) +is deliberately a **complete, on-disk set**: +[generalization-catalog-mapping.xml](../../Source/ISHRemote/Trisoft.ISHRemote/Samples/Data-GeneralizeDitaXml/generalization-catalog-mapping.xml), +`SpecializedDTDs/`, `GeneralizedDTDs/`, and `InputFiles/` (bookmap, reference, learning, subject +scheme). It serves two roles at once: a **runnable sample** *and* a **manual debugging set** for +developing/exercising the Generalize cmdlet (`New-IshDitaGeneralizedXml`). +- **Why checked in rather than generated:** in theory the XML catalogs, DTDs and example files could + be emitted by the test harness, but that would make them far harder to *use, inspect and explain* + later. So historically these stay as **self-contained files** you can open and reason about. +- **Keep them self-contained and coherent:** the catalog mapping, DTDs and input files must stay + internally consistent (the mapping resolves specialized → generalized DTDs by public id / system id + / root element). Carry forward the explanatory comments already in the mapping file. + +## 4. Some sample data is ALSO live Pester fixtures — don't break it +Parts of `Samples/` are referenced directly by the Pester suite via **relative paths**, so they are +load-bearing test fixtures, not just decoration: +- [NewIshDitaGeneralizedXml.Tests.ps1](../../Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/FileProcessor/NewIshDitaGeneralizedXml.Tests.ps1) + and + [TestIshValidXml.Tests.ps1](../../Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/FileProcessor/TestIshValidXml.Tests.ps1) + reach into `..\..\Samples\Data-GeneralizeDitaXml\SpecializedDTDs\…`, + `…\GeneralizedDTDs\…` and `…\generalization-catalog-mapping.xml`. +- The + [Remember--UsedByPesterTests.txt](../../Source/ISHRemote/Trisoft.ISHRemote/Samples/Data-GeneralizeDitaXml/Remember--UsedByPesterTests.txt) + marker exists precisely to flag this coupling. **Before renaming, moving, or deleting anything** + under such a folder, grep the `*.Tests.ps1` files for the path and update both sides together — or + leave it alone and ask the implementer. + +## 5. Don't +- Don't commit real customer URLs, hostnames, usernames, passwords, client ids/secrets, tokens, or + any tenant data — anonymize like the ReleaseNotes (see §1). +- Don't rename/move/delete files under a `Data-*` set without checking the Pester tests that + reference them by relative path (see §4). +- Don't "improve" a `Data-GeneralizeDitaXml` set by generating it at test time — its value is being + self-contained, readable and explainable (see §3). +- Don't treat samples as product code (no Apache header, not packaged, may be intentionally legacy); + keep them illustrative and runnable. diff --git a/.github/instructions/source-scripts--powershell.instructions.md b/.github/instructions/source-scripts--powershell.instructions.md new file mode 100644 index 0000000..8003721 --- /dev/null +++ b/.github/instructions/source-scripts--powershell.instructions.md @@ -0,0 +1,92 @@ +--- +applyTo: "Source/ISHRemote/Trisoft.ISHRemote/Scripts/**/*.*" +description: "Structure and intent of the ISHRemote Scripts layer: PowerShell advanced functions that ship inside the binary module. Public/ functions are part of the public library surface (approved Verb-IshNoun naming, PSScriptAnalyzer-clean, explicitly exported, StrictMode-safe) and each should gain a *.Tests.ps1 following the Pester instructions; Private/ holds internal Aux helpers." +--- + +# ISHRemote `Scripts/` Conventions + +`Scripts/` holds the **PowerShell advanced functions** that ship *inside* the otherwise-binary +ISHRemote module. They sit next to the C# cmdlets and are loaded by +[ISHRemote.psm1](../../Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.psm1), so to a user a function +here is indistinguishable from a compiled cmdlet. There are two tiers: +- **`Scripts/Public/`** — user-facing features that become part of the **public library surface** + (the MCP server family, `Expand-ISHParameter`, `Write-IshRemoteLog`). +- **`Scripts/Private/`** — internal **`*IshAux*` helpers** that support the public ones + (`Get-IshAuxSessionState`, `New-IshAuxCompletionResult`, `Register-IshAuxParameterCompleter`). + +> For the reviewer’s binary pass/flag checklist see +> [`source-codereview-powershell.instructions.md`](source-codereview-powershell.instructions.md). + +## 0. Public scripts ARE library surface — treat them like cmdlets +A `Public/` function is shipped and callable, so it carries the **same obligations as a C# cmdlet** +(see [source-cmdlets--csharp.instructions.md](./source-cmdlets--csharp.instructions.md)): +- **Approved `Verb-Noun` naming.** Use a verb from `Get-Verb` and the `Ish` noun convention. The + established families are `Verb-IshRemote*` for the MCP server (`Start-IshRemoteMcpServer`, + `Register-IshRemoteMcpTool`, `Register-IshRemoteMcpInstructions`, `Register-IshRemoteMcpResource`, + `Invoke-IshRemoteMcpHandleRequest`), plus `Write-IshRemoteLog` and the older + `Expand-ISHParameter`. Match a sibling rather than inventing a new shape. +- **It is a compatibility contract.** Renaming a public function or its parameters is a breaking + change — same bar as renaming a cmdlet parameter; **ask the implementer** first. +- **`Private/` helpers signal internal intent** by the `IshAux` infix; keep new internal helpers + there with that naming and don't promote them to a user-facing name casually. + +## 1. No Apache license header here +Unlike the C# tree, the `.ps1` files in `Scripts/` do **not** carry the Apache header — match the +neighbours. (Attribution `# Hat tip to …` comments for borrowed techniques are welcome, as in +[Expand-ISHParameter.ps1](../../Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Expand-ISHParameter.ps1).) + +## 2. File shape +- **One function per file, file name = function name** (`Start-IshRemoteMcpServer.ps1` → + `function Start-IshRemoteMcpServer`). The exception is a deliberately *side-effecting* script that + runs at dot-source time — [Expand-ISHParameter.ps1](../../Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Expand-ISHParameter.ps1) + patches `TabExpansion2` and registers argument completers rather than defining one exported + function. +- **Comment-based help** (`.SYNOPSIS` / `.DESCRIPTION` / `.PARAMETER` / `.EXAMPLE`) drives `Get-Help`, + and for MCP tools it is also **parsed into the tool/JSON schema** — so keep it accurate and + complete. Examples must be **anonymized** like the ReleaseNotes (`https://example.com/ISHWS/`, no + secrets). +- Use `[CmdletBinding()]` and typed `param(...)` with `[Parameter(Mandatory)]` where appropriate; + prefer singular parameter names, mirroring the cmdlet conventions. + +## 3. How they load and export (build gotchas) +- [ISHRemote.psm1](../../Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.psm1) **dot-sources every + `Scripts/Private/*.ps1` then `Scripts/Public/*.ps1`, excluding `*.Tests.ps1`**, and then runs + `Set-StrictMode -Version Latest`. → **Your script must be StrictMode-Latest clean** (no + use of unassigned variables, no implicit `$null` member access, etc.). +- **Exported functions are an explicit allow-list**, not folder-derived. The module manifest is + generated in + [Trisoft.ISHRemote.csproj](../../Source/ISHRemote/Trisoft.ISHRemote/Trisoft.ISHRemote.csproj) via + `New-ModuleManifest -FunctionsToExport @(...)`. **When you add a new function that users should + call, add its name to that `-FunctionsToExport` list** — otherwise it loads but isn't visible. + (Note `Expand-ISHParameter` is intentionally *not* exported: it does its work as a side effect at + import time.) +- Packaging copies `Scripts/**/*.*` into the published module **but excludes `*.Tests.ps1`** (both + casings) — so tests live beside the code in source yet never ship. + +## 4. Linting — keep `Scripts/**` PSScriptAnalyzer-clean +CI runs `Invoke-ScriptAnalyzer -Path Source/ISHRemote/Trisoft.ISHRemote/Scripts -Recurse`. It reports +but does **not** fail the build on findings — **still, keep it clean.** The most relevant rules here: +use approved verbs (`PSUseApprovedVerbs`), declare `[CmdletBinding()]`, avoid unapproved aliases, and +don't leave unused variables. A clean analyzer run is the bar for a PR. + +## 5. Every function should get a `*.Tests.ps1` +Each function should **eventually have a Pester counterpart** next to it +(`Start-IshRemoteMcpServer.Tests.ps1`, `Register-IshRemoteMcpTool.Tests.ps1`, …) that **follows the +rules in [source-cmdlets--pester.instructions.md](./source-cmdlets--pester.instructions.md)**: +`BeforeAll` sets `$cmdletName` and dot-sources `ISHRemote.PesterSetup.ps1` via the relative +`\..\..\` hop, `Describe`/`Context` structure, self-cleaning data, etc. +- Some script tests differ from the live-server acceptance suite: the MCP tests are **PowerShell 7+ + only** (`-Skip:($PSVersionTable.PSVersion.Major -lt 7)`) and may **`Mock`** collaborators (e.g. + `Write-IshRemoteLog`) so that pure-JSON assertions run without a reachable CMS. That is fine — they + still follow the same file/`BeforeAll` structure. +- Keep expected/asserted strings **anonymized** (the existing fixtures use + `https://example.com/ISHWS/`). + +## 6. Don't +- Don't rename/remove a `Public/` function or its parameters without implementer approval (breaks + scripts, help, and the MCP tool schema) — see §0. +- Don't forget to add a new exported function to `-FunctionsToExport` in the `.csproj` (§3). +- Don't write code that trips `Set-StrictMode -Version Latest` or PSScriptAnalyzer (§3, §4). +- Don't add an Apache header to `.ps1` files here (§1), and don't let `*.Tests.ps1` ship — the build + already excludes them. +- Don't put real customer URLs/secrets in help, examples, or test fixtures — anonymize (§2, §5). diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..3a5e4e1 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ +<!-- +Thanks for contributing to ISHRemote! Keep the PR small and focused. +Just describe your change below — the detailed conventions live in +.github/copilot-instructions.md and .github/instructions/*. +--> + +## What does this change do? +<!-- A few sentences. Why is it needed? --> + + +## Linked issue +<!-- "Closes #123" auto-closes the issue on merge. Leave blank if none. --> +Closes # + +## How was it tested? +<!-- e.g. "Release build is warning-clean" and/or "ran the cmdlet's *.Tests.ps1 against 15.3.0". + No live Tridion Docs server? Say so and a reviewer will verify. --> + + +--- +Before you submit, a quick sanity check (delete if not relevant): + +- No real URLs or secrets — use `example.com` placeholders, and don't commit `ISHRemote.PesterSetup.Debug.ps1`. +- New/changed cmdlet has a matching `*.Tests.ps1` and updated `///` help. +- Renaming a public cmdlet parameter or `Ish*` object member is a breaking change — flag it for the maintainer. diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..a41e4e8 --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,47 @@ +name: Code Quality + +# Fast, server-less repo-quality checks kept OUT of continuous-integration.yml, so that editing +# the public Doc/ or Samples/ material never triggers the heavy Windows build + serial +# live-server Pester acceptance suite (which needs secrets, cannot run in parallel and takes ~40m). +# +# CodeQL static analysis lives in its own codeql.yml. Dependabot is NOT a workflow - it is +# configured in .github/dependabot.yml and runs natively on GitHub's schedule. + +on: + push: + branches: [ master ] + paths: + - 'Doc/**' + - 'Source/ISHRemote/Trisoft.ISHRemote/Samples/**' + - 'Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/_TestEnvironment/TestAnonymization.Tests.ps1' + - '.github/workflows/code-quality.yml' + pull_request: + branches: [ master ] + paths: + - 'Doc/**' + - 'Source/ISHRemote/Trisoft.ISHRemote/Samples/**' + - 'Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/_TestEnvironment/TestAnonymization.Tests.ps1' + - '.github/workflows/code-quality.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + anonymization: + name: Anonymization of public Doc and Samples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Pester Test Anonymization (no live server needed) + shell: pwsh + run: | + if (-not (Get-Module -ListAvailable -Name Pester | Where-Object { $_.Version -ge [version]'5.3.0' })) { + Set-PSRepository PSGallery -InstallationPolicy Trusted + Install-Module Pester -MinimumVersion 5.3.0 -Force -SkipPublisherCheck -Scope CurrentUser + } + Import-Module Pester -MinimumVersion 5.3.0 + $result = Invoke-Pester -Path Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/_TestEnvironment/TestAnonymization.Tests.ps1 -Output Detailed -PassThru + if ($result.FailedCount -gt 0) { + throw "Anonymization guard failed: $($result.FailedCount) check(s) found a real URL, internal hostname, private IP or token. Use example.com placeholders or extend the known-exceptions list in the test." + } diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..01e7212 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,52 @@ +name: CodeQL + +# Static security analysis of the C# sources. Uses build-mode 'none' so it does NOT need the +# Windows-only multi-target build - it scans the sources directly on a fast Linux runner. +# PowerShell is not a CodeQL-supported language, so only the C# surface (auth, proxies, cmdlets, +# objects) is analyzed. Runs on C# changes and weekly. +# +# NOTE: This workflow uses an advanced configuration (build-mode: none). GitHub repository +# default CodeQL setup must be DISABLED for this workflow to avoid SARIF submission errors +# (CodeQL cannot process advanced config when default setup is enabled). See repository +# Settings → Code Security and Analysis → Code Scanning: ensure "Enable CodeQL" is OFF. + +on: + push: + branches: [ master ] + paths: + - 'Source/**/*.cs' + - '.github/workflows/codeql.yml' + pull_request: + branches: [ master ] + paths: + - 'Source/**/*.cs' + - '.github/workflows/codeql.yml' + schedule: + - cron: '0 6 * * 1' + workflow_dispatch: + +jobs: + analyze: + name: Analyze C# + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + contents: read + strategy: + fail-fast: false + matrix: + include: + - language: csharp + build-mode: none + steps: + - uses: actions/checkout@v5 + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 53531ff..53ab387 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -1,19 +1,30 @@ name: Continuous Integration +# Two-job design: +# build — fast lane: Release build + lint + serverless Pester. No secrets. Parallel-safe. +# Triggers on every push/PR to any branch. Uploads the compiled module as an artifact. +# live — live integration tests against the real Tridion Docs server. Needs secrets. +# Runs only after build succeeds and only for master-targeting pushes/PRs. +# Serialized via concurrency group so Dependabot PRs queue instead of racing. + on: push: - branches: [ master ] paths: - 'Source/**' - '*.TXT' - '*.MD' pull_request: - branches: [ master ] paths: - 'Source/**' - '*.TXT' - '*.MD' +# Least-privilege default: both jobs only need to read the repository contents +# (checkout) and use the actions runtime token for artifacts. No job writes back +# to the GitHub repository — PSGallery publish uses an external NUGET_API_KEY. +permissions: + contents: read + env: ISHGITHUB_RUN_NUMBER: ${{ github.run_number }} ISHGITHUB_SERVER_URL: ${{ github.server_url }} @@ -21,8 +32,12 @@ env: ISHGITHUB_RUN_ID: ${{ github.run_id }} jobs: - build: + # --------------------------------------------------------------------------- + # Fast lane — no secrets, parallel-safe, used by agents and Dependabot PRs. + # --------------------------------------------------------------------------- + build: + name: Build, lint and fast checks runs-on: windows-latest steps: @@ -47,12 +62,12 @@ jobs: echo "ISHGITHUB_RUN_ID[$env:ISHGITHUB_RUN_ID]" - uses: actions/checkout@v5 - + - name: Setup .NET 6.0.x uses: actions/setup-dotnet@v5 with: dotnet-version: 6.0.x - + - name: Explicit restore of multi-target/conditional ProjectReference and dependencies shell: pwsh run: | @@ -62,17 +77,20 @@ jobs: # dotnet restore Source/ISHRemote/Trisoft.ISHRemote.OpenApiISH30\Trisoft.ISHRemote.OpenApiISH30.NET48.csproj # dotnet restore Source/ISHRemote/Trisoft.ISHRemote.OpenApiISH30\Trisoft.ISHRemote.OpenApiISH30.NET60.csproj dotnet restore Source/ISHRemote/ISHRemote.sln - + - name: Build Solution shell: pwsh run: dotnet build --no-restore --no-incremental --configuration release Source/ISHRemote/ISHRemote.sln - - - name: Setup PowerShell PSScriptAnalyzer + + - name: Setup PowerShell modules (PSScriptAnalyzer + Pester) shell: pwsh run: | Set-PSRepository PSGallery -InstallationPolicy Trusted Install-Module PSScriptAnalyzer -ErrorAction Stop - + if (-not (Get-Module -ListAvailable -Name Pester | Where-Object { $_.Version -ge [version]'5.3.0' })) { + Install-Module Pester -MinimumVersion 5.3.0 -Force -SkipPublisherCheck -Scope CurrentUser + } + - name: Lint with PSScriptAnalyzer shell: pwsh run: | @@ -84,10 +102,46 @@ jobs: } else { Write-Output "There were $($errors.Count) errors and $($warnings.Count) warnings total." } - + + - name: Pester Test Anonymization of public Doc and Samples on Windows using PowerShell 7.x + shell: pwsh + run: | + $result = Invoke-Pester -Path Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/_TestEnvironment/TestAnonymization.Tests.ps1 -Output Detailed -PassThru + if ($result.FailedCount -gt 0) { + throw "Anonymization guard failed: $($result.FailedCount) check(s) found a real URL, internal hostname, private IP or token. Use example.com placeholders or extend the known-exceptions list in the test." + } + - name: Archive ISHRemote module - uses: actions/upload-artifact@v5 - with: + uses: actions/upload-artifact@v6 + with: + name: ISHRemote-MainCI-Module + path: Source/ISHRemote/Trisoft.ISHRemote/bin/Release/ISHRemote/ + + + # --------------------------------------------------------------------------- + # Live integration tests — requires the single shared Tridion Docs server. + # Queued (not cancelled) so Dependabot PRs and pushes never race each other. + # --------------------------------------------------------------------------- + live: + name: Live integration tests (PS 7.x and PS 5.1) + runs-on: windows-latest + needs: build + if: (github.event_name == 'push' && github.ref == 'refs/heads/master') || (github.event_name == 'pull_request' && github.base_ref == 'master') + concurrency: + group: run-tests-on-ishbaseurl + cancel-in-progress: false + + steps: + - name: PowerShell Update to latest stable (initially because 7.2.13/lts had issues, forcing latest stable) + uses: bjompen/UpdatePWSHAction@v1.0.1 + with: + ReleaseVersion: 'Stable' + + - uses: actions/checkout@v5 + + - name: Download ISHRemote module artifact + uses: actions/download-artifact@v4 + with: name: ISHRemote-MainCI-Module path: Source/ISHRemote/Trisoft.ISHRemote/bin/Release/ISHRemote/ @@ -124,7 +178,7 @@ jobs: ISH_CLIENT_SECRET: ${{ secrets.ISH_CLIENT_SECRET }} run: Invoke-Pester -Path Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/_TestEnvironment/TestPrerequisite.Tests.ps1 -Output Detailed -Passthru | Export-CliXml -Path Cmdlets.Pester.Tests.xml - name: Upload test results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: ISHRemote-MainCI-PesterPrerequisiteOnPowerShellCore path: Cmdlets.Pester.Tests.xml @@ -137,9 +191,9 @@ jobs: ISH_PASSWORD: ${{ secrets.ISH_PASSWORD }} ISH_CLIENT_ID: ${{ secrets.ISH_CLIENT_ID }} ISH_CLIENT_SECRET: ${{ secrets.ISH_CLIENT_SECRET }} - run: Invoke-Pester -Path @('Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/','Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/') -ExcludePath @('*GetIshDocumentObj.Tests.ps1') -Output Detailed -Passthru | Export-CliXml -Path Cmdlets.Pester.Tests.xml + run: Invoke-Pester -Path @('Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/','Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/') -ExcludePath @('*GetIshDocumentObj.Tests.ps1','*TestAnonymization.Tests.ps1') -Output Detailed -Passthru | Export-CliXml -Path Cmdlets.Pester.Tests.xml - name: Upload test results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: ISHRemote-MainCI-PesterOnPowerShellCore path: Cmdlets.Pester.Tests.xml @@ -178,7 +232,7 @@ jobs: ISH_CLIENT_SECRET: ${{ secrets.ISH_CLIENT_SECRET }} run: Invoke-Pester -Path Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/_TestEnvironment/TestPrerequisite.Tests.ps1 -Output Detailed -Passthru | Export-CliXml -Path Cmdlets.Pester.Tests.xml - name: Upload test results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: ISHRemote-MainCI-PesterPrerequisiteOnWindowsPowerShell path: Cmdlets.Pester.Tests.xml @@ -191,9 +245,9 @@ jobs: ISH_PASSWORD: ${{ secrets.ISH_PASSWORD }} ISH_CLIENT_ID: ${{ secrets.ISH_CLIENT_ID }} ISH_CLIENT_SECRET: ${{ secrets.ISH_CLIENT_SECRET }} - run: Invoke-Pester -Path @('Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/') -ExcludePath @('*GetIshDocumentObj.Tests.ps1') -Output Detailed -Passthru | Export-CliXml -Path Cmdlets.Pester.Tests.xml + run: Invoke-Pester -Path @('Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/') -ExcludePath @('*GetIshDocumentObj.Tests.ps1','*TestAnonymization.Tests.ps1') -Output Detailed -Passthru | Export-CliXml -Path Cmdlets.Pester.Tests.xml - name: Upload test results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: ISHRemote-MainCI-PesterOnWindowsPowerShell path: Cmdlets.Pester.Tests.xml diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..b9e98f0 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,17 @@ +{ + // Recommended extensions for ISHRemote development. + // VS Code will prompt "Do you want to install the recommended extensions?" when the + // workspace is opened for the first time. Install all of them to get: + // - C# IntelliSense, build errors inline, go-to-definition across the 3 projects + // - PowerShell syntax, debugger (wired to launch.json) and script analysis + // - GitHub Copilot with the .github/instructions/ auto-injection activated + "recommendations": [ + // C# — required to work with Cmdlets/, Objects/, Connection/, ExtensionMethods/ + "ms-dotnettools.csdevkit", + // PowerShell — required for Scripts/, *.Tests.ps1, and the launch.json debug configs + "ms-vscode.powershell", + // GitHub Copilot — activates .github/copilot-instructions.md and the instructions/ files + "GitHub.copilot", + "GitHub.copilot-chat" + ] +} diff --git a/.vscode/mcp.json b/.vscode/mcp.json index 9475240..3b5e877 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -1,22 +1,41 @@ { - "servers": { - "IshRemoteMcpServer": { - "type": "stdio", - "command": "pwsh", - "args": [ - "-NoProfile", - "-Command", - "& { Start-IshRemoteMcpServer -CmdletsToRegister ((Get-Command -Module ISHRemote -ListImported -CommandType Cmdlet).Name) -LogFilePath \"D:\\GITHUB\\ISHRemote\\IshRemoteMcpServer.log\" }" - ] - }, - "IshRemoteMcpServerDEBUG": { - "type": "stdio", - "command": "pwsh", - "args": [ - "-NoProfile", - "-Command", - "${workspaceFolder}\\.vscode\\Debug-IShRemoteMCPServer.ps1" - ] - } - } + "servers": { + "IshRemoteMcpServer": { + "type": "stdio", + "command": "pwsh", + "args": [ + "-NoProfile", + "-Command", + "& { Start-IshRemoteMcpServer -CmdletsToRegister ((Get-Command -Module ISHRemote -ListImported -CommandType Cmdlet).Name) -LogFilePath \"${workspaceFolder}\\IshRemoteMcpServer.log\" }" + ] + }, + "IshRemoteMcpServerDEBUG": { + "type": "stdio", + "command": "pwsh", + "args": [ + "-NoProfile", + "-Command", + "${workspaceFolder}\\.vscode\\Debug-IShRemoteMCPServer.ps1" + ] + }, + "microsoft-learn": { + "type": "http", + "url": "https://learn.microsoft.com/api/mcp" + }, + "github": { + "type": "http", + "url": "https://api.githubcopilot.com/mcp/", + "headers": { + "Authorization": "Bearer ${input:github-pat}" + } + } + }, + "inputs": [ + { + "id": "github-pat", + "type": "promptString", + "description": "GitHub Personal Access Token (scopes: repo, security_events)", + "password": true + } + ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..a688c3b --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,90 @@ +{ + // Task names intentionally mirror the CI job/step names in + // .github/workflows/continuous-integration.yml so that a developer running + // them locally gets the same semantic as the CI "Build, lint and fast checks" job. + // ...but deliberately the debug build, and not release build. + "version": "2.0.0", + "tasks": [ + { + // Mirrors CI step: "Explicit restore of multi-target/conditional ProjectReference and dependencies" + // Must be run at least once after a clean checkout; re-run whenever .csproj files change. + "label": "Restore Solution", + "type": "shell", + "command": "dotnet", + "args": [ + "restore", + "Source/ISHRemote/ISHRemote.sln" + ], + "group": "build", + "presentation": { + "echo": true, + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": "$msCompile" + }, + { + // Mirrors CI step: "Build Solution" + // Uses Release configuration because TreatWarningsAsErrors=true only fires in Release. + // Run "Restore Solution" once first if you see CS0234/CS0518 namespace errors. + "label": "Build Solution", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "--no-incremental", + "--configuration", "debug", + "Source/ISHRemote/ISHRemote.sln" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "echo": true, + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": "$msCompile" + }, + { + // Mirrors CI step: "Lint with PSScriptAnalyzer" + // Requires PSScriptAnalyzer: Install-Module PSScriptAnalyzer -ErrorAction Stop + "label": "Lint with PSScriptAnalyzer", + "type": "shell", + "command": "pwsh", + "args": [ + "-NonInteractive", + "-Command", + "Invoke-ScriptAnalyzer -Path Source/ISHRemote/Trisoft.ISHRemote/Scripts -Recurse -OutVariable issues; $errors=$issues.Where({$_.Severity -eq 'Error'}); $warnings=$issues.Where({$_.Severity -eq 'Warning'}); Write-Output \"Errors[$($errors.Count)] Warnings[$($warnings.Count)]\"; $issues | Format-Table" + ], + "group": "test", + "presentation": { + "echo": true, + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + // Mirrors the CI job name: "Build, lint and fast checks" + // Runs all three steps in order: restore → build (Release) → lint. + // This is the local equivalent of the CI fast lane (no live-server tests). + "label": "Build, lint and fast checks", + "dependsOn": [ + "Restore Solution", + "Build Solution", + "Lint with PSScriptAnalyzer" + ], + "dependsOrder": "sequence", + "group": "build", + "presentation": { + "echo": true, + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": "$msCompile" + } + ] +} diff --git a/Doc/McpInstructions-ISHRemote.md b/Doc/McpInstructions-ISHRemote.md new file mode 100644 index 0000000..ec34450 --- /dev/null +++ b/Doc/McpInstructions-ISHRemote.md @@ -0,0 +1,136 @@ +# Introduction + +IShRemoteMcpServer is a Model Context Provider implementation on top of PowerShell module IShRemote. The IShRemote module, and in turn all supporting PowerShell constructs, can be used to define an answer. Every IShRemote cmdlet has been made available as an MCP Tool to use; where the parameters of the cmdlet became the parameters of the MCP Tool. + +The IShRemote module is used by many clients in PowerShell PS1 script files to achieve business automation on top of a Tridion Docs CMS (Content Management System). Such a Tridion Docs environment, often simplified to server, is typically identified by its `-WsBaseUrl "https://example.com/ISHWS/"` or variation. + + +# Getting Started + +To execute these MCP Tools, so IShRemote cmdlets, you need to create a `IshSession`. If not you will see MCP Tools responding with `IshSession is null. Please create a session first using New-IshSession`. The cmdlet `New-IShSession` with provided `-WsBaseUrl` https url is triggered first - potenially with `-ClientId` and `-ClientSecret` parameters if provided. The result is an in-memory IShSession object that implicitly set as a variable accessible over `$ISHRemoteSessionStateIshSession`. This means that you never have to explicitly mention parameter `-IShSession` or value `$ishSession` because every cmdlet takes that implicitly from `$ISHRemoteSessionStateIshSession`. + + +# Rules + +1. IShRemote PowerShell cmdlets can be recognized by their naming convention as the noun starts with `ISh`, so verb-`ISh`noun like `New-IShSession`. Most of the cmdlets of IShRemote are made available as MCP Tools with the same or reduced parameter set - for example parameter `-IshSession` with value `$ishSession` is superfluous and should be avoided. Do not pass parameter `-IshSession` to any MCP Tool. +2. All ISHRemote MCP Tools are allowed to be loaded, use `Get-Help` to help resolve the request. Do not make the user individually activate groups of MCP Tools. +3. Do not make up parameters for the cmdlets! Do not use any JSON parameters! Do not use hash table structures (`@{ Name = "FUSERNAME"; Value = ...}`) for ISHRemote cmdlets! All parameters should exist and can be verified over the MCP Tool description or by triggering ISHRemote MCP Tool `Get-Help` cmdlet on it. +4. Do not make up field names or imagine their field level! You must use `Get-IshTypeFieldDefinition` to understand the available field and object types, including custom fields. You can reuse the `Get-IshTypeFieldDefinition` result until a `New-IShSession` was triggered to save tokens. Do not ask for `Continue` for this `Get-IshTypeFieldDefinition` MCP Tool. +5. Respect the casing of parameters, fields and properties! PowerShell, and in turn ISHRemote, is case-sensitive! +6. The verb of PowerShell cmdlet indicates if it about read or write operations. Write operations like `Set` are about updating an existing CMS entity, `Add` and `Remove` about creating and deleting them. Read operations like `Find` and `Get` retrieve information over the primary relational database and should in turn be used with caution as a `Find` can theoretically return you the full CMS repository leading to out-of-memory. Therefor `Set-IshMetadataFilterField` usage is strongly advised to do server-side filtering as much as possible. Note that the wildcard operator for `like` and `cilike` is percentage (`%`) and not asterix (`*`). The `Search` retrieves information over the secondary full text index for `IshDocumentObj` and is considered faster than the `Find`. +7. Read operations, typically starting with `Get`, `Find` or `Search` can be executed by you to verify the returned fields and PSNoteProperty properties. Always verify the outcome of read operations (like `Find-IshOutputFormat`) before attempting any write operations (like `Set-IshOutputFormat`). This ensures you are targeting the correct entity and prevents accidental changes. +8. All IShRemote cmdlets come with preformatted tables, there is no need to pipeline (`|`) these results in a `Format-Table` cmdlet. +9. You can always do a `Get-Help` for any ISHRemote cmdlet with option `-Detailed` so you get insights in its purpose, parameter sets which are allowed parameter combinations, the individual parameters explained and of course several typical examples. Do not ask for `Continue` for this `Get-Help` MCP Tool. +10. `Get-IShUser` without parameters is the shallow `whoami` call that checks if you are still authenticated. If not do a `New-IShession` again. + + +# Concepts + + +## Entities Hierarchy + +Essentially the Tridion Docs product, often called InfoShare, uses the following entity types inside its database. +- Fields are in essence key value pairs, where the key is often prefixed with `F` short for field and `FISH` for product system fields like examples `FAUTHOR` or `FISHLINKS`. The value can be + - `String` (`STRING`), where a string field is advised to be lower than 1333 characters. Its database storage allows rich filtering over `like` (case-sensitive) and `cilike` (case-insensitive) operators. Important to note that the wildcard operator for `like` and `cilike` is percentage (`%`) and not asterix (`*`). + - `LongText` (`LONG TEXT`), is also a string field where the value can be over 1333 characters. However, the operators to filter are limited to `empty` or `notempty` + - `ISHMetadataBinding`, is also a string field but holds identifiers from the remotely linked taxonomy system. + - `Number` (`NUMBER`), holds digits potentially with decimal separator. Many operators are available as listed under section `FilterOperator`. + - `DateTime` (`DATE`), holds a date and time representation. Many operators are available as listed under section `FilterOperator`. + - `ISHLov` (`LOV`), short for List-of-Values or sometimes called domain or permitted values, is a controlled list of allowed values. This enumeration has values identified by a `label` and an idempotent `element` identifier. The `ValueType` `label` as created through cmdlet `Add-IshLovValue` is allowed to change afterwards over cmdlet `Set-IshLovValue`, hence not advised to use in API calls. The `ValueType` `element`, once created is stable and outlives label renames. + - `ISHType` (`CARD`), similar to List-of-Values, however this time the permitted values are of the type of the linked card type. Example is `IShUser` to indentity that a field like `FAUTHOR` links to an existing user instance. Same for an `IShFolder` that needs to link over `FUSERGROUP` to an existing `IShUserGroup`. +- A CARD like system, where CARD instance holds fields with values of the type LOV, DATE, NUMBER, STRING or LONG TEXT. +- A single CARD instance holding fields is identified by `Level` `none`. This level is implicit as there is no confusion on which field to address on this card instance. Examples of this type are `IShUser`, `IShUserGroup`, `IShUserRole`, `IShConfiguration`, `IShOutputFormat`, `IShEDT` or `IShTranslationJob`. The typical identifier (property `IShRef`), is a generated somewhat readable identifier. For example `VUSERADMIN` which is prefix `V` for value, then `USER` for the object type and `ADMIN` is a proprietary unicode encoding of the label provided at creation type. +- A multi CARD instance holding fields is identified by its `Level`. As there is potential confusion of having the same field name on multiple levels, like `MODIFIED-ON` (which holds the last modified date of that card level instance), it is advised to be explicit in specifying levels. The typical identifier (property `IShRef`), is a Global Unique Identifier (GUID), for the top entity like `logical`. + - `IShPublication`, `IShDocumentObj` and their derived types use three levels: `logical`, `version` and `lng` (language). + - `IShAnnotation` uses levels: `Annotation` or `Reply` + - `ISHProject` uses levels: `Project` and `ProjectAssignee` +- There is also a TABLE like system where the columns of the tables are expressed as field names and the alias names of the table are expressed as levels. + - `IShBaseline` has a TABLE subsystem known as `IShBaselineItem` + - `IShTranslationJob` has a TABLE subsystem known as `IShTranslationJobItem` + - `IShEvent` has a TABLE subsystem with Level `Progress` and `Detail` + - `IShBackgroundTask` has a TABLE subsystem with Level `Task` and `History` + +These base entities surface over the business and api code to the ISHRemote library as the below Field Types and Object Types which can be interacted with. + + +## Field Types + +There is a hierarchy where ISHRemote base class `IShField`, that represents the field `Name` and `Level`, is extended to +- `IShRequestedMetadataField` where `Name` and `Level` is reused and enriched with `ValueType` which highlights by `Value` the typical label of an entity or by `Element` the idempotent identifier of an entity. +- `IShMetadataField` where `Name` and `Level` is reused and enriched with `ValueType` which highlights by `Value` the typical label of an entity or by `Element` the idempotent identifier of an entity. Another property added here is `Value` which represent the value of the metadata field. +- `IShMetadataFilterField` where `Name` and `Level` is reused and enriched with `ValueType` which highlights by `Value` the typical label of an entity or by `Element` the idempotent identifier of an entity. Another property added here is `Value` which represent the value of the metadatafield. The last property is `FilterOperator` which an enumeration of allowed API operators. + +## Object Types + +Most object types are simple classes to return values like `IShSession` or `IShTypeFieldDefinition`. + +There is a hierarchy where base class `IShObject` is used for PowerShell pipeline actions, either as input or output for a cmdlet. Derived classes are known as `ISHType` as listed + - `IShAnnotation` holds an instance with fields (`IShField`) of level `Annotation` or `Reply` that refer to comments, annotations and suggestions. + - `IShBaseline` holds an instance with fields (`IShField`) of level `None` and can be linked to `IShBaselineItem` entries that list exact versions per logical identifier. + - `IShConfiguration` holds the singleton instance with fields (`IShField`) of level `None` that holds feature toggles, Xml Settings and more system configuration. + - `IShEDT` holds an instance with fields (`IShField`) of level `None`. EDT is short for Electronic Document Type that links a typical file extension and mimetype of some binary. Well known EDT is `EDTXML` for xml files and `EDTPNG` for png files. This information is also offered when downloading Data (`IShData`). + - `IShFolder` holds an instance with fields (`IShField`) of level `None`. Folders or sometimes called Directories are nested structures to organize `IShPublication` and `IShDocumentObj` derived entities. Depth 1 returns the current folder, depth 2 first level of subfolders and typically requires the recurse parameter. + - `IShDocumentObj`, also known as content objects. A denormalized structure of the `logical`, `version` and `lng` (language) level fields (`IShField`). + - `IShIllustration` in turn is derived from `IShDocumentObj` and holds content objects known as images, illustrations, graphics. A unique language level identifier for this type is lng level field resolution (`FRESOLUTION`) which indicates the purpose of an image - for example `Low` for web resolution, `High` for print quality and `Master` could be the source vector graphic that generated the earlier two. + - `IShLibrary` in turn is derived from `IShDocumentObj` and holds content objects known as library topics, conref libraries or variable libraries. Typically holding OASIS DITA topic xml files of type `EDTXML`. + - `IShMasterDoc` in turn is derived from `IShDocumentObj` and holds content objects known as master documents, maps, bookmaps. Typically holding OASIS DITA map xml files of type `EDTXML`. + - `IShModule` in turn is derived from `IShDocumentObj` and holds content objects known as topics, concepts, tasks or reference topics. Typically holding OASIS DITA topic xml files of type `EDTXML`. + - `IShTemplate` in turn is derived from `IShDocumentObj` and holds content objects known as templates of type 'Other'. Not to be confused with 'Editor Templates'. Typically holding the various binary files for supporting resources that need storing in the CMS. + - `IShOutputFormat` holds an instance with fields (`IShField`) of level `None`. Output formats is a controlled list of rendering outputs indicating a certain layout or format. Typical examples are 'PDF A4' (so PDF files of page size A4) or 'Dynamic Delivery' (so downstream DXD or Genius web delivery format). Overall the output format is a key in identifying the language level of publication. + - `IShProject` holds an instance with fields (`IShField`) of level `Project` and `ProjectAssignee`. A project is an entity in the publication hub to identify active publications to users. + - `IShPublication`, also known as publication output. A denormalized structure of the `logical`, `version` and `lng` (language) level fields (`IShField`). The language level is commenly known as publication output as it uniquely identifies the logical, version and language level of a publication. This all the way to the selected output format. + - `IShTranslationJob` holds an instance with fields (`IShField`) of level `None` and can be linked to `IShTranslationJobItem` entries that list exact versions per logical identifier. + - `IShUser` holds an instance with fields (`IShField`) of level `None`. A user is the entity used to assign authoring (`FAUTHOR`) or reviewing (`FREVIEWER`) work to. When creating a `New-IShSession` you will be authenticated to a user instance, which in turn gives you authorization for CMS actions. + - `IShUserGroup` holds an instance with fields (`IShField`) of level `None`. A user group is assigned to protect `IShFolder` with read and write permission. This read and write security is saved on all `IShPublication` and `IShDocumentObj` derived entities that are organized in those folders. In turn an `IShUser` needs user groups assigned to be able to read or update folders or entities in those folders. + - `IShUserRole` holds an instance with fields (`IShField`) of level `None`. A user role is assigned to an `IShUser` to authorize the user for activities in the CMS. The power user role is called `Administrator` and can be considered CMS root access. User Roles can have one or more priviliges, also known as permissions, assigned to enable users specific actions. + +## Requesting metadata, Get-IshTypeFieldDefinition and Set-IshRequestedMetadataField + +ISHRemote offers defaults for the optional `-RequestedMetadata` parameter that many cmdlets, so MCP Tools, have. The default is calculated as taking the `Get-IshTypeFieldDefinition` for the current Object Type. So cmdlets returning data like reading `Find-IShUser` or even creating `Add-IShUser` which returns the created instance will filter the table on column `ISHType` to `IShUser` and `SDB` column needs to hold `B`. + +The `Get-IshTypeFieldDefinition` returns the object types, field types and more in a table format. The columns should be interpreted as follows. +* `ISHType` column as explained under object types like `IShIllustration`, `IShFolder`, `IShUser` and more. +* `Level` column as explained under field types indicates if the mentioned field is linked to a single (`None`) or hierarchy level. +* `Name` holds the name of a standard or custom field like `FAUTHOR`. +* `DataType` column as explained under entity types like `String`, `LongText`, `ISHLov`, `ISHType`, etc +* `DataSource` column only holds a value for `DataType` entries `ISHLov`, `ISHType` and `ISHMetadataBinding`. When `ISHLov` the data source entry holds the list-of-values name like `DSTATUS` which can be further queried over `Get-IshLovValue` cmdlets. When `ISHType` the data source entry holds the object type that this object links to like field `FUSERGROUP` on `ISHType` `ISHFolder` will hold an `ISHUserGroup` reference or `FAUTHOR` on `ISHIllustration` will hold an `ISHUser` reference. When `ISHMetadataBinding` the data source holds an information setting value, the only important piece is that this field can only filtered using the `-ValueType` `Element` option. +* `SDB` column will indicate `B` for Basic, and `D` for Descriptive (so only object identifiers) and `S` for product system fields. When the field is not identified as one of these a hyphen `-` will appear. For example field `FSTATUS` is identified as `S-B` so a System field, not a Descriptive field and considered Basic field to work with this `ISHType`. +* `MM` column where the first letter is either `M` indicating mandatory or hyphen `-` indicating not. The second letter is either `1` indicating that the field can only hold a single value, or `n` indicating that the field can hold multiple values. These multiple values are typical when a `DataSource` is used to store multiple selections. +* `CRUST` column where the first letter either is `C` or hyphen `-` indicating that the field can be passed on an `Add` cmdlet while creating it. Second letter `R` or hyphen `-` indicating that the field can be passed as requested metadata - for example passwords fields cannot be read. Third letter `U` or hyphen `-` indicating that the field can be passed as metadata to update the entity. Fourth letter `S` or hyphen `-` indicates if the field is searchable in the full text index. Fifth letter `T` or hyphen `-` indicating if the field is SmartTagging enabled over `ISHMetadataBinding. +* `Description` holds an informational description of the named field. + +## Filtering metadata, FilterOperator and Set-IshMetadataFilterField + +Below table is replicated from public documentation [Understanding metadata filter operators](https://docs.rws.com/en-US/tridion-docs-main-documentation-1165616/understanding-metadata-filter-operators-68174). The first column holds the allowed filter operators of the `-FilterOperator` parameter that is used on the `Set-IshMetadataFilterField` cmdlet. + +| | ISHType | ISHLov | DateTime | Number | String | LongText | +| --- | --- | --- | --- | --- | --- | --- | +| `equal` | X | X | X | X | X | | +| `not equal` | X | X | | X | X | | +| `in` | X | X | X | X | X | | +| `not in` | X | X | | X | X | | +| `like` | X | X | | | X | | +| `cilike` | | | | | X | | +| `greater than` | | | X | X | | | +| `less than` | | | X | X | | | +| `greater than or equal` | | | X | X | | | +| `less than or equal` | | | X | X | | | +| `between` | | | X | X | | | +| `empty` | X | X | X | X | X | X | +| `not empty` | X | X | X | X | X | X | + +Where `ISHMetadataBinding` typically is a multi-value `String` field holding the remote taxonomy identifiers (`element`), the CMS does not store the `label`s as they are retrieved live from the remote taxonomy. + +## Updating metadata and Set-IshMetadataField + +All Object Types mentioned as output of a cmdlet are client side in-memory representations. If you would update the fields of this object using `Set-IshMetadataField`, then you updated the client side in-memory representation, it would still require the matching `Set` operation (like object `IshUser` requires `Set-IshUser`). + +## Reading metadata, using PSNoteType properties + +The returned in-memory objects hold the requested implicit or explicitly requested metadata over parameter `-RequestedMetadata`. To access the returned fields `IShField` of a card instance, one would historically use cmdlet `Get-IshMetadataField`. A much more convenient system is to use the generated read-only PSNoteType properties which also provides the dates in sortable ISO8601 format. + +To predict the naming of PSNoteType properties you can use a quick `Format-List` on the returned in-memory object like `$ishObject | Format-List`. This will show the lower-cased and underscore seperated field name, field level and value type. +* The commonly used field levels `None`, `Task`, `Progress` and `Lng` are omitted. The same goes for value type `value` which is also omitted. As an example field `FISHUSERTYPE` on `IShUser` will be offered as read-only property `fishusertype` holding the human-readable value `Internal`. +* When there is doubt the full level and value type is used in lower-case format. To continue the example the read-only property `fishusertype_none_element` holding the stable identifier `VUSERTYPEINTERNAL` (so a `V` list of value entry of `DUSERTYPE´). + +Note these PSNoteType properties are read-only, they can only be used to read or process values. Any change like updating metadata needs to go over `Set-IshMetadataField`. diff --git a/README.MD b/README.MD index 2f297e2..0cb7d93 100644 --- a/README.MD +++ b/README.MD @@ -67,6 +67,8 @@ Any feedback is welcome. Please log a GitHub issue, make sure you submit your ve # Standards To Respect +> Detailed authoring rules and reviewer checklists are maintained in [`.github/instructions/`](.github/instructions/) and are auto-injected when editing the relevant paths (`Cmdlets/**/*.cs`, `**/*.Tests.ps1`, `Scripts/**/*.ps1`). The summaries below remain for quick human reference. + ## Coding Standards * Any code change should diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/AddIshAnnotation.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/AddIshAnnotation.Tests.ps1 index ee55c9f..9894915 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/AddIshAnnotation.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/AddIshAnnotation.Tests.ps1 @@ -103,11 +103,11 @@ Describe "Add-IshAnnotation" -Tags "Create" { } } - Context "Add-IshAnnotation ParametersGroup" { + Context "Add-IshAnnotation ParameterGroup" { BeforeAll { $revisionId = $ishObjectTopic.ed $annotationAddress = "[{""revisionId"":""$revisionId"",""startContainerQuery"":""/*[1]/node()[1]/node()[1]"",""startOffset"":0,""endContainerQuery"":""/*[1]/node()[1]/node()[1]"",""endOffset"":4,""type"":""TEXT_RANGE_SELECTOR""}]" - $annotationText = "by ISHRemote Pester [ParametersGroup] on $timestamp" + $annotationText = "by ISHRemote Pester [ParameterGroup] on $timestamp" $annotationCategory = (Get-IshLovValue -LovId DANNOTATIONCATEGORY -LovValueId VANNOTATIONCATEGORYCOMMENT).Label $annotationType = (Get-IshLovValue -LovId DANNOTATIONTYPE -LovValueId VANNOTATIONTYPEGENERAL).Label $annotationStatus = (Get-IshLovValue -LovId DANNOTATIONSTATUS -LovValueId VANNOTATIONSTATUSUNSHARED).Label @@ -162,11 +162,11 @@ Describe "Add-IshAnnotation" -Tags "Create" { } } - Context "Add-IshAnnotation ParametersGroup override metadata matching parameters" { + Context "Add-IshAnnotation ParameterGroup override metadata matching parameters" { BeforeAll { $revisionId = $ishObjectTopic.ed $annotationAddress = "[{""revisionId"":""$revisionId"",""startContainerQuery"":""/*[1]/node()[1]/node()[1]"",""startOffset"":0,""endContainerQuery"":""/*[1]/node()[1]/node()[1]"",""endOffset"":4,""type"":""TEXT_RANGE_SELECTOR""}]" - $annotationText = "by ISHRemote Pester [ParametersGroup] on $timestamp" + $annotationText = "by ISHRemote Pester [ParameterGroup] on $timestamp" $annotationCategory = (Get-IshLovValue -LovId DANNOTATIONCATEGORY -LovValueId VANNOTATIONCATEGORYCOMMENT).Label $annotationType = (Get-IshLovValue -LovId DANNOTATIONTYPE -LovValueId VANNOTATIONTYPEGENERAL).Label $annotationStatus = (Get-IshLovValue -LovId DANNOTATIONSTATUS -LovValueId VANNOTATIONSTATUSUNSHARED).Label diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/AddIshAnnotation.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/AddIshAnnotation.cs index 6d715d0..9b3f807 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/AddIshAnnotation.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/AddIshAnnotation.cs @@ -113,7 +113,7 @@ public sealed class AddIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshAnnotationGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "MetadataGroup")] @@ -123,7 +123,7 @@ public sealed class AddIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">Publication LogicalId</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public string PubLogicalId { get; set; } @@ -131,7 +131,7 @@ public sealed class AddIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">Publication version</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public string PubVersion { get; set; } @@ -139,7 +139,7 @@ public sealed class AddIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">Publication language</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public string PubLng { get; set; } @@ -147,28 +147,28 @@ public sealed class AddIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">LogicalId of the content object</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public string LogicalId { get; set; } /// <summary> /// <para type="description">Version of the content object</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public string Version { get; set; } /// <summary> /// <para type="description">Language of the content object</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public string Lng { get; set; } /// <summary> /// <para type="description">Type of the annotation</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public string Type { get; set; } @@ -176,7 +176,7 @@ public sealed class AddIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">Text of the annotation</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public string Text { get; set; } @@ -184,7 +184,7 @@ public sealed class AddIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">Status of the annotation</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public string Status { get; set; } @@ -192,7 +192,7 @@ public sealed class AddIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">Address of the annotation</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public string Address { get; set; } @@ -200,7 +200,7 @@ public sealed class AddIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">Category of the annotation</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public string Category { get; set; } @@ -208,7 +208,7 @@ public sealed class AddIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">The metadata of the Annotation</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "MetadataGroup")] [ValidateNotNullOrEmpty] @@ -273,7 +273,7 @@ protected override void ProcessRecord() returnFields = metadata; } - if (ParameterSetName == "ParametersGroup" || ParameterSetName == "IshObjectGroup") + if (ParameterSetName == "ParameterGroup" || ParameterSetName == "IshObjectGroup") { // 1.1. Get the latest RevisionId for the given LogicalId/Version/Language of the content object var metadata = (Metadata == null) ? new IshFields() : new IshFields(Metadata); @@ -294,7 +294,7 @@ protected override void ProcessRecord() } else { - // ParametersGroup + // ParameterGroup var response = IshSession.DocumentObj25.GetMetadata(new GetMetadataRequest(LogicalId, Version, Lng, diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/GetIshAnnotation.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/GetIshAnnotation.Tests.ps1 index 36df5fe..d133aed 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/GetIshAnnotation.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/GetIshAnnotation.Tests.ps1 @@ -134,7 +134,7 @@ Describe "Get-IshAnnotation" -Tags "Create" { $annotationIdsP1P2 = @($ishAnnotation1P1.IshRef, $ishAnnotation2P1.IshRef,$ishAnnotation1P2.IshRef, $ishAnnotation2P2.IshRef) $annotationIdsP1P2P3 = @($ishAnnotation1P1.IshRef, $ishAnnotation2P1.IshRef,$ishAnnotation1P2.IshRef, $ishAnnotation2P2.IshRef, $ishAnnotation1P3.IshRef) } - Context "Get-IshAnnotation ParametersGroup" { + Context "Get-IshAnnotation ParameterGroup" { It "Parameter AnnotationId is an empty array" { {Get-IshAnnotation -IshSession $ishsession -AnnotationId @()} | Should -Throw } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/GetIshAnnotation.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/GetIshAnnotation.cs index a5f5217..956a78f 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/GetIshAnnotation.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/GetIshAnnotation.cs @@ -75,7 +75,7 @@ public sealed class GetIshAnnotation : AnnotationCmdlet /// <summary> /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshAnnotationGroup")] [ValidateNotNullOrEmpty] @@ -84,21 +84,21 @@ public sealed class GetIshAnnotation : AnnotationCmdlet /// <summary> /// <para type="description">Annotation Ids</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public string[] AnnotationId { get; set; } /// <summary> /// <para type="description">The metadata filter with the filter fields to limit the amount of annotations returned. Default is no filtering.</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public IshField[] MetadataFilter { get; set; } /// <summary> /// <para type="description">The metadata fields to retrieve</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshAnnotationGroup")] [ValidateNotNullOrEmpty] @@ -243,7 +243,7 @@ protected override void EndProcessing() annotationIds = foundObjects.Select(ishObject => Convert.ToString(ishObject.IshRef)).ToList(); } - if (ParameterSetName == "ParametersGroup") + if (ParameterSetName == "ParameterGroup") { annotationIds = AnnotationId.ToList(); } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/RemoveIshAnnotation.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/RemoveIshAnnotation.Tests.ps1 index 8dcd753..5d06ca6 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/RemoveIshAnnotation.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/RemoveIshAnnotation.Tests.ps1 @@ -48,7 +48,7 @@ Describe "Remove-IshAnnotation" -Tags "Create" { $annotationType = (Get-IshLovValue -LovId DANNOTATIONTYPE -LovValueId VANNOTATIONTYPEGENERAL).Label $annotationStatus = (Get-IshLovValue -LovId DANNOTATIONSTATUS -LovValueId VANNOTATIONSTATUSUNSHARED).Label - # Add annotations - ParametersGroup + # Add annotations - ParameterGroup $ishAnnotationPG1 = Add-IshAnnotation -IshSession $ishsession -PubLogicalId $ishObjectPub.IshRef -PubVersion $ishObjectPub.version_version_value -PubLng $ishObjectPub.fishpubsourcelanguages_version_value -LogicalId $ishObjectTopic.IshRef -Version $ishObjectTopic.version_version_value -Lng $ishObjectTopic.doclanguage -Type $annotationType -Text $annotationText -Status $annotationStatus -Category $annotationCategory -Address $annotationAddress $ishAnnotationPGWithReplies = Add-IshAnnotation -IshSession $ishsession -PubLogicalId $ishObjectPub.IshRef -PubVersion $ishObjectPub.version_version_value -PubLng $ishObjectPub.fishpubsourcelanguages_version_value -LogicalId $ishObjectTopic.IshRef -Version $ishObjectTopic.version_version_value -Lng $ishObjectTopic.doclanguage -Type $annotationType -Text $annotationText -Status $annotationStatus -Category $annotationCategory -Address $annotationAddress $strMetadataReply = "<ishfields><ishfield name='FISHANNOTATIONTEXT' level='reply'>reply to an annotation $($ishAnnotationPGWithReplies.IshRef)</ishfield></ishfields>" # Deliberate xml to pass straight as string on the proxy function @@ -90,7 +90,7 @@ Describe "Remove-IshAnnotation" -Tags "Create" { $ishAnnotationReply = $ishsession.Annotation25.CreateReply($ishAnnotationPipelineMixedWithReplies.IshRef, $strMetadataReply) } - Context "Remove-IshAnnotation ParametersGroup" { + Context "Remove-IshAnnotation ParameterGroup" { It "Parameter AnnotationId is empty" { {Remove-IshAnnotation -IshSession $ishsession -AnnotationId ""} | Should -Throw } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/RemoveIshAnnotation.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/RemoveIshAnnotation.cs index b3ce79a..acd62cd 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/RemoveIshAnnotation.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/RemoveIshAnnotation.cs @@ -50,7 +50,7 @@ public sealed class RemoveIshAnnotation : AnnotationCmdlet /// <summary> /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshAnnotationGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -58,7 +58,7 @@ public sealed class RemoveIshAnnotation : AnnotationCmdlet /// <summary> /// <para type="description">AnnotationId</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public string AnnotationId { get; set; } @@ -142,7 +142,7 @@ protected override void EndProcessing() //1. Prepare list of annotations to delete depending on the chosen ParameterSet switch(ParameterSetName) { - case "ParametersGroup": + case "ParameterGroup": annotationIdsToDelete.Add(AnnotationId); break; case "IshAnnotationGroup": diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/SetIshAnnotation.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/SetIshAnnotation.Tests.ps1 index 0cc58e3..f6c69c4 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/SetIshAnnotation.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/SetIshAnnotation.Tests.ps1 @@ -49,7 +49,7 @@ Describe "Set-IshAnnotation" -Tags "Create" { $annotationStatus = (Get-IshLovValue -LovId DANNOTATIONSTATUS -LovValueId VANNOTATIONSTATUSUNSHARED).Label $annotationStatusShared = (Get-IshLovValue -LovId DANNOTATIONSTATUS -LovValueId VANNOTATIONSTATUSSHARED).Label - # Add 2 annotations - ParametersGroup + # Add 2 annotations - ParameterGroup $ishAnnotationPG1 = Add-IshAnnotation -IshSession $ishsession -PubLogicalId $ishObjectPub.IshRef -PubVersion $ishObjectPub.version_version_value -PubLng $ishObjectPub.fishpubsourcelanguages_version_value -LogicalId $ishObjectTopic.IshRef -Version $ishObjectTopic.version_version_value -Lng $ishObjectTopic.doclanguage -Type $annotationType -Text $annotationText -Status $annotationStatus -Category $annotationCategory -Address $annotationAddress $ishAnnotationPGWithReplies = Add-IshAnnotation -IshSession $ishsession -PubLogicalId $ishObjectPub.IshRef -PubVersion $ishObjectPub.version_version_value -PubLng $ishObjectPub.fishpubsourcelanguages_version_value -LogicalId $ishObjectTopic.IshRef -Version $ishObjectTopic.version_version_value -Lng $ishObjectTopic.doclanguage -Type $annotationType -Text $annotationText -Status $annotationStatus -Category $annotationCategory -Address $annotationAddress $strMetadataReply = "<ishfields><ishfield name='FISHANNOTATIONTEXT' level='reply'>reply to an annotation $($ishAnnotationPGWithReplies.IshRef)</ishfield></ishfields>" # Deliberate xml to pass straight as string on the proxy function @@ -63,7 +63,7 @@ Describe "Set-IshAnnotation" -Tags "Create" { $ishAnnotationReply = $ishsession.Annotation25.CreateReply($ishAnnotationIAGWithReplies.IshRef, $strMetadataReply) $ishAnnotationReply = $ishsession.Annotation25.CreateReply($ishAnnotationIAGWithReplies.IshRef, $strMetadataReply) } - Context "Set-IshAnnotation ParametersGroup" { + Context "Set-IshAnnotation ParameterGroup" { It "Parameter AnnotationId is empty" { {Set-IshAnnotation -IshSession $ishsession -AnnotationId "" -Metadata (Set-IshMetadataField -Name "FISHANNOTATIONTEXT" -Level Annotation -Value "update should fail")} | Should -Throw } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/SetIshAnnotation.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/SetIshAnnotation.cs index c046c6d..be1aaa6 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/SetIshAnnotation.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Annotation/SetIshAnnotation.cs @@ -65,7 +65,7 @@ public sealed class SetIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshAnnotationGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -73,14 +73,14 @@ public sealed class SetIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">Id of the Annotation</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public string AnnotationId { get; set; } /// <summary> /// <para type="description">The metadata of the Annotation</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshAnnotationGroup")] [ValidateNotNullOrEmpty] public IshField[] Metadata { get; set; } @@ -88,7 +88,7 @@ public sealed class SetIshAnnotation :AnnotationCmdlet /// <summary> /// <para type="description">The required current metadata of the Annotation</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParametersGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshAnnotationGroup")] [ValidateNotNullOrEmpty] public IshField[] RequiredCurrentMetadata { get; set; } @@ -194,7 +194,7 @@ protected override void EndProcessing() returnFields = (_ishAnnotationsToSet[0] == null) ? new IshFields() : _ishAnnotationsToSet[0].IshFields; break; - case "ParametersGroup": + case "ParameterGroup": WriteDebug($"AnnotationId[{AnnotationId}] Metadata.length[{metadata.ToXml().Length}]"); if (ShouldProcess(AnnotationId)) { diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/BackgroundTask/AddIshBackgroundTask.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/BackgroundTask/AddIshBackgroundTask.Tests.ps1 index 4c02522..70d2dda 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/BackgroundTask/AddIshBackgroundTask.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/BackgroundTask/AddIshBackgroundTask.Tests.ps1 @@ -38,7 +38,7 @@ Describe "Add-IshBackgroundTask" -Tags "Create" { $ishObjects = $ishFolderTopic | Get-IshFolderContent -IshSession $ishSession -VersionFilter "" $createdLngRefs = $ishObjects | select -ExpandProperty LngRef } - Context "Add-IshBackgroundTask IshObjectsGroup Parameter IshObject with implicit IshSession since 14SP4/14.0.4" { + Context "Add-IshBackgroundTask IshObjectGroup Parameter IshObject with implicit IshSession since 14SP4/14.0.4" { It "Parameter IshObject invalid" { if (([Version]$ishSession.ServerVersion).Major -ge 15 -or (([Version]$ishSession.ServerVersion).Major -ge 14 -and ([Version]$ishSession.ServerVersion).Revision -ge 4)) { { Add-IshBackgroundTask -EventType $ishEventTypeToPurge -IshObject "INVALIDISHOBJECT" } | Should -Throw @@ -70,7 +70,7 @@ Describe "Add-IshBackgroundTask" -Tags "Create" { } } - Context "Add-IshBackgroundTask IshObjectsGroup Pipeline IshObject since 14SP4/14.0.4" { + Context "Add-IshBackgroundTask IshObjectGroup Pipeline IshObject since 14SP4/14.0.4" { BeforeAll { if (([Version]$ishSession.ServerVersion).Major -ge 15 -or (([Version]$ishSession.ServerVersion).Major -ge 14 -and ([Version]$ishSession.ServerVersion).Revision -ge 4)) { $ishBackgroundTaskIshObjectsPipeline = $ishObjects | Add-IshBackgroundTask -IshSession $ishSession -EventType $ishEventTypeToPurge @@ -108,7 +108,7 @@ Describe "Add-IshBackgroundTask" -Tags "Create" { } } - Context "Add-IshBackgroundTask IshObjectsGroup Pipeline IshObject with InputDataTemplate" { + Context "Add-IshBackgroundTask IshObjectGroup Pipeline IshObject with InputDataTemplate" { BeforeAll { $requestedMetadata = Set-IshRequestedMetadataField -IshSession $ishSession -Level Task -Name INPUTDATAID } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/BackgroundTask/AddIshBackgroundTask.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/BackgroundTask/AddIshBackgroundTask.cs index 7890e4e..ee1cb56 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/BackgroundTask/AddIshBackgroundTask.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/BackgroundTask/AddIshBackgroundTask.cs @@ -35,7 +35,7 @@ namespace Trisoft.ISHRemote.Cmdlets.BackgroundTask /// <summary> /// <para type="synopsis">The Add-IshBackgroundTask cmdlet adds fire-and-forget asynchronous processing events to the CMS generic queuing system.</para> /// <para type="description">Add-IshBackgroundTask ParameterGroup variation uses BackgroundTask25.CreateBackgroundTask(WithStartAfter) that allows you to submit generic messages. Note that this requires a generic BackgroundTask service message handler.</para> - /// <para type="description">Add-IshBackgroundTask IshObjectsGroup requires content object(s) which are transformed as message inputdata and passed to the Background Task.</para> + /// <para type="description">Add-IshBackgroundTask IshObjectGroup requires content object(s) which are transformed as message inputdata and passed to the Background Task.</para> /// </summary> /// <example> /// <code> @@ -104,7 +104,7 @@ public sealed class AddIshBackgroundTask : BackgroundTaskCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -112,7 +112,7 @@ public sealed class AddIshBackgroundTask : BackgroundTaskCmdlet /// <para type="description">Type of the event (e.g. SMARTTAG). Needs a match CMS BackgroundTask service handler entry.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public string EventType { get; set; } @@ -127,7 +127,7 @@ public sealed class AddIshBackgroundTask : BackgroundTaskCmdlet /// <para type="description">Description of the event</para> /// </summary> [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public string EventDescription { get; set; } @@ -135,27 +135,27 @@ public sealed class AddIshBackgroundTask : BackgroundTaskCmdlet /// <para type="description">Date time indicating that the background task should not be picked up and executed before it.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] public DateTime? StartAfter { get; set; } /// <summary> /// <para type="description">The <see cref="IshObjects"/>s that will be used for background task creation.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } /// <summary> /// <para type="description">The InputDataTemplate (e.g. IshObjectWithLngRef) indicates whether a list of ishObjects or one ishObject is submitted as input data to the background task.</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] public InputDataTemplate InputDataTemplate { get; set; } = InputDataTemplate.IshObjectsWithLngRef; /// <summary> /// <para type="description">The hash id of the background task.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateLength(0, 80)] public string HashId { get; set; } @@ -341,7 +341,7 @@ protected override void EndProcessing() maximumProgress = _startEventMaxProgress }; - var backgroundTaskInputData = Encoding.Unicode.GetBytes(ParameterSetName == "IshObjectsGroup" ? inputData : RawInputData); + var backgroundTaskInputData = Encoding.Unicode.GetBytes(ParameterSetName == "IshObjectGroup" ? inputData : RawInputData); if (HashId == null) { @@ -351,7 +351,7 @@ protected override void EndProcessing() if (StartAfter.HasValue) { // Create BackgroundTask - var message = ParameterSetName == "IshObjectsGroup" ? + var message = ParameterSetName == "IshObjectGroup" ? $"Create BackgroundTask EventType[{EventType}] InputData.length[{inputData.Length}] StartAfter[{StartAfter}]" : $"Create BackgroundTask EventType[{EventType}] RawInputData.length[{RawInputData.Length}] StartAfter[{StartAfter}]"; WriteDebug(message); @@ -375,7 +375,7 @@ protected override void EndProcessing() if (!StartAfter.HasValue) { // Create BackgroundTask - var message = ParameterSetName == "IshObjectsGroup" ? + var message = ParameterSetName == "IshObjectGroup" ? $"Create BackgroundTask EventType[{EventType}] InputData.length[{inputData.Length}]" : $"Create BackgroundTask EventType[{EventType}] RawInputData.length[{RawInputData.Length}]"; WriteDebug(message); diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/AddIshBaseline.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/AddIshBaseline.cs index 3047a00..9fed2a4 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/AddIshBaseline.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/AddIshBaseline.cs @@ -44,14 +44,14 @@ public sealed class AddIshBaseline : BaselineCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } /// <summary> /// <para type="description">Array with the baselines to create. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/AddIshBasline.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/AddIshBasline.Tests.ps1 index 81a9a72..7f24c0b 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/AddIshBasline.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/AddIshBasline.Tests.ps1 @@ -31,7 +31,7 @@ Describe "Add-IshBaseline" -Tags "Create" { $ishObject.fishdocumentrelease_none_element.StartsWith('GUID') | Should -Be $true } } - Context "Add-IshBaseline IshObjectsGroup" { + Context "Add-IshBaseline IshObjectGroup" { BeforeAll { $baselineName = ($cmdletName + " " + (Get-Date -Format "yyyyMMddHHmmssfff") + " A") $ishObjectA = Add-IshBaseline -IshSession $ishSession -Name $baselineName | @@ -79,7 +79,7 @@ Describe "Add-IshBaseline" -Tags "Create" { $ishObjects.Count | Should -Be 2 } } - Context "Add-IshBaseline IshObjectsGroup" { + Context "Add-IshBaseline IshObjectGroup" { BeforeAll { $baselineName = ($cmdletName + " " + (Get-Date -Format "yyyyMMddHHmmssfff") + " A") $ishObjectA = Add-IshBaseline -IshSession $ishSession -Name $baselineName | diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaseline.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaseline.Tests.ps1 index 241b28f..da32048 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaseline.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaseline.Tests.ps1 @@ -62,7 +62,7 @@ Describe "Get-IshBaseline" -Tags "Read" { } } - Context "Get-IshBaseline IshObjectsGroup" { + Context "Get-IshBaseline IshObjectGroup" { BeforeAll { $baselineName = ($cmdletName + " " + (Get-Date -Format "yyyyMMddHHmmssfff") + " A") $ishObjectA = Add-IshBaseline -IshSession $ishSession -Name $baselineName diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaseline.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaseline.cs index 6c65c94..9591367 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaseline.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaseline.cs @@ -54,7 +54,7 @@ public sealed class GetIshBaseline : BaselineCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -68,7 +68,7 @@ public sealed class GetIshBaseline : BaselineCmdlet /// <para type="description">The metadata filter with the filter fields to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] MetadataFilter { get; set; } @@ -76,14 +76,14 @@ public sealed class GetIshBaseline : BaselineCmdlet /// <para type="description">The metadata fields to retrieve</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] RequestedMetadata { get; set; } /// <summary> /// <para type="description">Array with the baselines for which to retrieve the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaselineItem.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaselineItem.Tests.ps1 index 51a9eb1..48dccb5 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaselineItem.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaselineItem.Tests.ps1 @@ -60,7 +60,7 @@ Describe "Get-IshBaselineItem" -Tags "Read" { $bi[0].ModifiedOnAsSortableDateTime.Substring(8,2) | Should -Be $day } } - Context "Get-IshBaselineItem IshObjectsGroup" { + Context "Get-IshBaselineItem IshObjectGroup" { BeforeAll { $baselineName = ($cmdletName + " " + (Get-Date -Format "yyyyMMddHHmmssfff") + " A") $ishObjectA = Add-IshBaseline -IshSession $ishSession -Name $baselineName diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaselineItem.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaselineItem.cs index 8a00014..76b0374 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaselineItem.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/GetIshBaselineItem.cs @@ -46,7 +46,7 @@ public sealed class GetIshBaselineItem : BaselineCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -59,7 +59,7 @@ public sealed class GetIshBaselineItem : BaselineCmdlet /// <summary> /// <para type="description">Array with the baselines for which to retrieve the baseline entries. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/RemoveIshBaseline.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/RemoveIshBaseline.cs index 5425c98..b45dbc6 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/RemoveIshBaseline.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/RemoveIshBaseline.cs @@ -43,7 +43,7 @@ public sealed class RemoveIshBaseline : BaselineCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -56,7 +56,7 @@ public sealed class RemoveIshBaseline : BaselineCmdlet /// <summary> /// <para type="description">Array with the baselines to remove. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/SetIshBaseline.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/SetIshBaseline.Tests.ps1 index 670bf0d..79aefe9 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/SetIshBaseline.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/SetIshBaseline.Tests.ps1 @@ -34,7 +34,7 @@ Describe "Set-IshBaseline" -Tags "Create" { } } - Context "Set-IshBaseline IshObjectsGroup" { + Context "Set-IshBaseline IshObjectGroup" { BeforeAll { $baselineName = ($cmdletName + " " + (Get-Date -Format "yyyyMMddHHmmssfff") + " A") $ishObjectA = Add-IshBaseline -IshSession $ishSession -Name $baselineName diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/SetIshBaseline.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/SetIshBaseline.cs index 7723f33..3fddf1e 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/SetIshBaseline.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Baseline/SetIshBaseline.cs @@ -46,7 +46,7 @@ public sealed class SetIshBaseline : BaselineCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -65,7 +65,7 @@ public sealed class SetIshBaseline : BaselineCmdlet /// <summary> /// <para type="description">User groups for which to update the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/AddIshDocumentObj.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/AddIshDocumentObj.Tests.ps1 index ca02915..b67fd85 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/AddIshDocumentObj.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/AddIshDocumentObj.Tests.ps1 @@ -139,7 +139,7 @@ Describe "Add-IshDocumentObj" -Tags "Create" { $ishObject.LngRef -gt 0 | Should -Be $true } } - Context "Add-IshDocumentObj IshObjectsGroup" { + Context "Add-IshDocumentObj IshObjectGroup" { BeforeAll { $ishTopicMetadata = Set-IshMetadataField -IshSession $ishSession -Name "FTITLE" -Level Logical -Value "Topic $timestamp" | Set-IshMetadataField -IshSession $ishSession -Name "FAUTHOR" -Level Lng -ValueType Element -Value $ishUserAuthor | diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/AddIshDocumentObj.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/AddIshDocumentObj.cs index 93447dc..dc39cdb 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/AddIshDocumentObj.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/AddIshDocumentObj.cs @@ -117,7 +117,7 @@ public sealed class AddIshDocumentObj : DocumentObjCmdlet /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFilePath")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFileContent")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -126,7 +126,7 @@ public sealed class AddIshDocumentObj : DocumentObjCmdlet /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFilePath")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFileContent")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public long FolderId { @@ -139,7 +139,7 @@ public long FolderId /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFilePath")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFileContent")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshFolder IshFolder { @@ -237,7 +237,7 @@ public string Edt /// <summary> /// <para type="description">The <see cref="IshObjects"/>s that need to be added.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/GetIshDocumentObj.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/GetIshDocumentObj.cs index 6385967..ed124f7 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/GetIshDocumentObj.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/GetIshDocumentObj.cs @@ -47,7 +47,7 @@ public sealed class GetIshDocumentObj : DocumentObjCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -61,7 +61,7 @@ public sealed class GetIshDocumentObj : DocumentObjCmdlet /// <para type="description">The metadata filter with the filter fields to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshField[] MetadataFilter { get; set; } @@ -69,7 +69,7 @@ public sealed class GetIshDocumentObj : DocumentObjCmdlet /// <para type="description">The metadata fields to retrieve</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshField[] RequestedMetadata { get; set; } @@ -77,7 +77,7 @@ public sealed class GetIshDocumentObj : DocumentObjCmdlet /// <para type="description">The status filter to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public Enumerations.StatusFilter StatusFilter { @@ -89,7 +89,7 @@ public Enumerations.StatusFilter StatusFilter /// <para type="description">Switch patameter that specifies if the return objects should hold the blob (ishdata) sections, potentially conditionally published if IshFeature was passed</para> /// </summary> [Parameter(Mandatory = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ParameterSetName = "IshObjectGroup")] public SwitchParameter IncludeData { get { return _includeData; } @@ -100,13 +100,13 @@ public SwitchParameter IncludeData /// <para type="description">The condition context to use for conditional filtering. If no context is provided, the elements containing ishcondition attributes will always remain in the data content. You can use the Set-IshFeature cmdlet to create a condition context.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] public IshFeature[] IshFeature { get; set; } /// <summary> /// <para type="description">Array with the objects for which to retrieve the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/MoveIshDocumentObj.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/MoveIshDocumentObj.cs index 239e25c..5935699 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/MoveIshDocumentObj.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/MoveIshDocumentObj.cs @@ -48,7 +48,7 @@ public sealed class MoveIshDocumentObj : DocumentObjCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -62,7 +62,7 @@ public sealed class MoveIshDocumentObj : DocumentObjCmdlet /// <para type="description">The folder identifier where the objects are currently located</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public long FromFolderId { @@ -74,7 +74,7 @@ public long FromFolderId /// <para type="description">The IshFolder object where the objects are currently located</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshFolder FromIshFolder { @@ -86,7 +86,7 @@ public IshFolder FromIshFolder /// <para type="description">The folder identifier where to move the document objects to</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public long ToFolderId { @@ -98,7 +98,7 @@ public long ToFolderId /// <para type="description">The IshFolder object where to move the document objects to</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshFolder ToIshFolder { @@ -109,7 +109,7 @@ public IshFolder ToIshFolder /// <summary> /// <para type="description">Array with the object to move. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] public IshObject IshObject { get; set; } #region Private fields diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/RemoveIshDocumentObj.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/RemoveIshDocumentObj.Tests.ps1 index b4a7a3e..2feba2d 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/RemoveIshDocumentObj.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/RemoveIshDocumentObj.Tests.ps1 @@ -145,7 +145,7 @@ Describe "Remove-IshDocumentObj" -Tags "Delete" { } } - Context "Remove-IshDocumentObj remove object (IshObjectsGroup)" { + Context "Remove-IshDocumentObj remove object (IshObjectGroup)" { It "IshObject passed via pipeline (Topic). Force=Yes" { $ishTopicMetadata = $ishObjectMetadata | Set-IshMetadataField -IshSession $ishSession -Name "FTITLE" -Level Logical -Value "$($____Pester.CurrentTest.Name) $timestamp" $ishObject = Add-IshDocumentObj -IshSession $ishSession -IshFolder $ishFolderTopic -IshType ISHModule -Version '1' -Lng $ishLng -Metadata $ishTopicMetadata -Edt "EDTXML" -FileContent $ditaTopicFileContent diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/RemoveIshDocumentObj.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/RemoveIshDocumentObj.cs index 598df7a..efcb6a8 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/RemoveIshDocumentObj.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/RemoveIshDocumentObj.cs @@ -50,7 +50,7 @@ public sealed class RemoveIshDocumentObj : DocumentObjCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -84,7 +84,7 @@ public sealed class RemoveIshDocumentObj : DocumentObjCmdlet /// <para type="description">If the metadata is not the same anymore, an error is given and the document object is not removed.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshField[] RequiredCurrentMetadata { get; set; } @@ -92,14 +92,14 @@ public sealed class RemoveIshDocumentObj : DocumentObjCmdlet /// <para type="description">Array with the objects to remove. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> /// TODO [Should] Promote parameter IshObject to IshObject[] processing - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] public IshObject IshObject { get; set; } /// <summary> /// <para type="description">When the Force switch is set, after deleting the language object, the version object and logical object will be deleted if they don't have any language objects anymore</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public SwitchParameter Force { get; set; } @@ -129,7 +129,7 @@ protected override void ProcessRecord() IshFields requiredCurrentMetadata = new IshFields(RequiredCurrentMetadata); - if (ParameterSetName == "IshObjectsGroup") + if (ParameterSetName == "IshObjectGroup") { long lngRef = Convert.ToInt64(IshObject.ObjectRef[Enumerations.ReferenceType.Lng]); WriteDebug($"Removing lngCardId[{lngRef}]"); diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/SetIshDocumentObj.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/SetIshDocumentObj.Tests.ps1 index 0e8d0fb..aab9668 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/SetIshDocumentObj.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/SetIshDocumentObj.Tests.ps1 @@ -645,7 +645,7 @@ Describe "Set-IshDocumentObj" -Tags "Create" { $fileInfoToUpdate.Length -lt $fileInfo.Length | Should -Be $true } } - Context "Set-IshDocumentObj IshObjectsGroup" { + Context "Set-IshDocumentObj IshObjectGroup" { BeforeAll { $ishTopicMetadata = Set-IshMetadataField -IshSession $ishSession -Name "FTITLE" -Level Logical -Value "Topic $timestamp" | Set-IshMetadataField -IshSession $ishSession -Name "FAUTHOR" -Level Lng -ValueType Element -Value $ishUserAuthor | diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/SetIshDocumentObj.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/SetIshDocumentObj.cs index ee0a762..605912f 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/SetIshDocumentObj.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/SetIshDocumentObj.cs @@ -119,7 +119,7 @@ public sealed class SetIshDocumentObj : DocumentObjCmdlet /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFilePath")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFileContent")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupMetadata")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -165,7 +165,7 @@ public sealed class SetIshDocumentObj : DocumentObjCmdlet /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFilePath")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFileContent")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupMetadata")] [ValidateNotNull] public IshField[] Metadata { get; set; } @@ -177,7 +177,7 @@ public sealed class SetIshDocumentObj : DocumentObjCmdlet /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFilePath")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupFileContent")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroupMetadata")] [ValidateNotNullOrEmpty] public IshField[] RequiredCurrentMetadata { get; set; } @@ -209,7 +209,7 @@ public string Edt /// <summary> /// <para type="description">Array with the objects for which to update the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } @@ -253,7 +253,7 @@ protected override void ProcessRecord() List<IshObject> returnedObjects = new List<IshObject>(); IshFields requiredCurrentMetadata = new IshFields(RequiredCurrentMetadata); - if (ParameterSetName == "IshObjectsGroup") + if (ParameterSetName == "IshObjectGroup") { int current = 0; IshObject[] ishObjects = IshObject; diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/AddIshEDT.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/AddIshEDT.cs index a08ccfc..c64ac7e 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/AddIshEDT.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/AddIshEDT.cs @@ -49,7 +49,7 @@ public sealed class AddIshEdt : EDTCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -68,7 +68,7 @@ public sealed class AddIshEdt : EDTCmdlet /// <summary> /// <para type="description">IshObject - contains EDT data that needs to be created. Pipeline</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/GetIshEDT.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/GetIshEDT.cs index 13ef961..f852512 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/GetIshEDT.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/GetIshEDT.cs @@ -57,7 +57,7 @@ public sealed class GetIshEdt : EDTCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -71,7 +71,7 @@ public sealed class GetIshEdt : EDTCmdlet /// <para type="description">The activity filter to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public Enumerations.ActivityFilter ActivityFilter { @@ -83,7 +83,7 @@ public Enumerations.ActivityFilter ActivityFilter /// <para type="description">The metadata filter with the filter fields to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] MetadataFilter { get; set; } @@ -91,14 +91,14 @@ public Enumerations.ActivityFilter ActivityFilter /// <para type="description">The metadata fields to retrieve</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] RequestedMetadata { get; set; } /// <summary> /// <para type="description">Array with the objects for which to retrieve the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/RemoveIshEDT.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/RemoveIshEDT.cs index e880d05..08afa6c 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/RemoveIshEDT.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/RemoveIshEDT.cs @@ -51,7 +51,7 @@ public sealed class RemoveIshEDT : EDTCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -64,7 +64,7 @@ public sealed class RemoveIshEDT : EDTCmdlet /// <summary> /// <para type="description">The IshObject array containing EDTs that needs to be deleted. Pipeline</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/SetIshEDT.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/SetIshEDT.cs index 7ec360f..d094ef1 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/SetIshEDT.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/EDT/SetIshEDT.cs @@ -60,7 +60,7 @@ public sealed class SetIshEdt : EDTCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -82,7 +82,7 @@ public sealed class SetIshEdt : EDTCmdlet /// <para type="description">If the metadata is still the same, the metadata will be set.</para> /// <para type="description">If the metadata is not the same anymore, an error is given and the metadata will be set.</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public IshField[] RequiredCurrentMetadata { get; set; } @@ -90,7 +90,7 @@ public sealed class SetIshEdt : EDTCmdlet /// <summary> /// <para type="description">Array with the objects for which to retrieve the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Field/SetIshField.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Field/SetIshField.cs index 0c72fe3..89ba5d2 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Field/SetIshField.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/Field/SetIshField.cs @@ -53,7 +53,7 @@ public sealed class SetIshField : FieldCmdlet /// <summary> /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshFieldGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -61,14 +61,14 @@ public sealed class SetIshField : FieldCmdlet /// <summary> /// <para type="description">The IshField[] to merge</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshFieldGroup")] public IshField[] MergeField { get; set; } /// <summary> /// <para type="description">Whether the fields in the mergefields parameter will be prepended, appended or overwrite the values in the provided IshFields or IshObject objects</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshFieldGroup")] public Enumerations.ValueAction ValueAction { @@ -79,7 +79,7 @@ public Enumerations.ValueAction ValueAction /// <summary> /// <para type="description">Objects to merge the fields in</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/AddIshOutputFormat.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/AddIshOutputFormat.Tests.ps1 index 875dd22..d4ebfd2 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/AddIshOutputFormat.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/AddIshOutputFormat.Tests.ps1 @@ -90,7 +90,7 @@ Describe "Add-IshOutputFormat" -Tags "Create" { $ishSession.StrictMetadataPreference = $strictMetadataPreference } } - Context "Add-IshOutputFormat IshObjectsGroup" { + Context "Add-IshOutputFormat IshObjectGroup" { BeforeAll { $metadata = Set-IshMetadataField -IshSession $ishSession -Name "FISHCLEANUP" -Level None -ValueType Element -Value "TRUE" | Set-IshMetadataField -IshSession $ishSession -Name "FISHKEEPDTDSYSTEMID" -Level None -ValueType Element -Value "TRUE" | diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/AddIshOutputFormat.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/AddIshOutputFormat.cs index 02ad048..a8b4fa6 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/AddIshOutputFormat.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/AddIshOutputFormat.cs @@ -54,7 +54,7 @@ public sealed class AddIshOutputFormat : OutputFormatCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -79,7 +79,7 @@ public sealed class AddIshOutputFormat : OutputFormatCmdlet /// <summary> /// <para type="description">IshObject - contains OutputFormat data that needs to be created. Pipeline</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/GetIshOutputFormat.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/GetIshOutputFormat.cs index 59eff03..50ee94e 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/GetIshOutputFormat.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/GetIshOutputFormat.cs @@ -49,7 +49,7 @@ public sealed class GetIshOutputFormat : OutputFormatCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -63,7 +63,7 @@ public sealed class GetIshOutputFormat : OutputFormatCmdlet /// <para type="description">The activity filter to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public Enumerations.ActivityFilter ActivityFilter { @@ -75,7 +75,7 @@ public Enumerations.ActivityFilter ActivityFilter /// <para type="description">The metadata filter with the filter fields to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] MetadataFilter { get; set; } @@ -83,14 +83,14 @@ public Enumerations.ActivityFilter ActivityFilter /// <para type="description">The metadata fields to retrieve</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] RequestedMetadata { get; set; } /// <summary> /// <para type="description">Output formats for which to retrieve the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/RemoveIshOutputFormat.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/RemoveIshOutputFormat.cs index 0e6ad80..37a22e8 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/RemoveIshOutputFormat.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/RemoveIshOutputFormat.cs @@ -53,7 +53,7 @@ public sealed class RemoveIshOutputFormat : OutputFormatCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -66,7 +66,7 @@ public sealed class RemoveIshOutputFormat : OutputFormatCmdlet /// <summary> /// <para type="description">The IshObject array containing OutputFormats that needs to be deleted. Pipeline</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/SetIshOutputFormat.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/SetIshOutputFormat.cs index facf09f..3025e2e 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/SetIshOutputFormat.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/OutputFormat/SetIshOutputFormat.cs @@ -50,7 +50,7 @@ public sealed class SetIshOutputFormat : OutputFormatCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -76,7 +76,7 @@ public sealed class SetIshOutputFormat : OutputFormatCmdlet /// <summary> /// <para type="description">Output formats for which to update the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/AddIshPublicationOutput.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/AddIshPublicationOutput.cs index b5aaa3c..09f85d5 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/AddIshPublicationOutput.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/AddIshPublicationOutput.cs @@ -55,14 +55,14 @@ public sealed class AddIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } /// <summary> /// <para type="description">The <see cref="Objects.Public.IshObject"/>s that need to be added.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } @@ -70,7 +70,7 @@ public sealed class AddIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">The FolderId for the PublicationOutput object.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public long FolderId { @@ -82,7 +82,7 @@ public long FolderId /// <para type="description">The FolderId of the PublicationOutput by IshFolder object</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshFolder IshFolder { diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutput.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutput.cs index f3e1d83..6359948 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutput.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutput.cs @@ -67,7 +67,7 @@ public sealed class GetIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -80,7 +80,7 @@ public sealed class GetIshPublicationOutput : PublicationOutputCmdlet /// <summary> /// <para type="description">Array with the publication outputs for which to retrieve the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } @@ -105,7 +105,7 @@ public Enumerations.StatusFilter StatusFilter /// <para type="description">The metadata fields to retrieve.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] RequestedMetadata { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/PublishIshPublicationOutput.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/PublishIshPublicationOutput.cs index 917be5f..1578e48 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/PublishIshPublicationOutput.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/PublishIshPublicationOutput.cs @@ -49,14 +49,14 @@ public sealed class PublishIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } /// <summary> /// <para type="description">Array with the publications outputs to publish. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } @@ -89,7 +89,7 @@ public sealed class PublishIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">If the metadata is still the same, the publication output is published.</para> /// <para type="description">If the metadata is not the same anymore, an error is given and the publication output is not published.</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public IshField[] RequiredCurrentMetadata { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/RemoveIshPublicationOutput.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/RemoveIshPublicationOutput.cs index 145a7d2..095decc 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/RemoveIshPublicationOutput.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/RemoveIshPublicationOutput.cs @@ -51,14 +51,14 @@ public sealed class RemoveIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } /// <summary> /// <para type="description">Array with the publication outputs to remove. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } @@ -91,7 +91,7 @@ public sealed class RemoveIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">If the metadata is still the same, the publication output is removed.</para> /// <para type="description">If the metadata is not the same anymore, an error is given and the publication output is not removed.</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public IshField[] RequiredCurrentMetadata { get; set; } @@ -100,7 +100,7 @@ public sealed class RemoveIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">When the Force switch is set, after deleting the publication output object, the publication version object and publication logical object will be deleted if they don't have any publication outputs anymore. /// Be carefull using this option, as it will delete the publication (version) and baseline if there are no outputformats left!!</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public SwitchParameter Force { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/SetIshPublicationOutput.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/SetIshPublicationOutput.cs index 8dafdb0..f6e0b35 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/SetIshPublicationOutput.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/SetIshPublicationOutput.cs @@ -59,14 +59,14 @@ public sealed class SetIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } /// <summary> /// <para type="description">Publication outputs for which to update the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } @@ -98,7 +98,7 @@ public sealed class SetIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">The metadata to set for the publication output object.</para> /// </summary> [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] Metadata { get; set; } @@ -108,7 +108,7 @@ public sealed class SetIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">If the metadata is not the same anymore, an error is given and the metadata will be set.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshField[] RequiredCurrentMetadata { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/StopIshPublicationOutput.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/StopIshPublicationOutput.cs index 7a1319e..60ffb81 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/StopIshPublicationOutput.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/StopIshPublicationOutput.cs @@ -75,14 +75,14 @@ public sealed class StopIshPublicationOutput : PublicationOutputCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } /// <summary> /// <para type="description">Array with the publication outputs to cancel. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } @@ -115,7 +115,7 @@ public sealed class StopIshPublicationOutput : PublicationOutputCmdlet /// If the metadata is still the same, the publication output is cancelled. /// If the metadata is not the same anymore, an error is given and the publication output is not cancelled.</para> /// </summary> - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] [ValidateNotNullOrEmpty] public IshField[] RequiredCurrentMetadata { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/AddIshUser.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/AddIshUser.Tests.ps1 index 6a75c87..88da6cc 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/AddIshUser.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/AddIshUser.Tests.ps1 @@ -120,7 +120,7 @@ Describe "Add-IshUser" -Tags "Create" { $ishSession.StrictMetadataPreference = $strictMetadataPreference } } - Context "Add-IshUser IshObjectsGroup" { + Context "Add-IshUser IshObjectGroup" { BeforeAll { $metadata = Set-IshMetadataField -IshSession $ishSession -Name FISHUSERLANGUAGE -Level None -ValueType Element -Value "VLANGUAGEEN" | Set-IshMetadataField -IshSession $ishSession -Name FUSERGROUP -Level None -ValueType Element -Value "VUSERGROUPDEFAULTDEPARTMENT" | diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/GetIshUser.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/GetIshUser.cs index abfc87a..99d5c9b 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/GetIshUser.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/GetIshUser.cs @@ -45,7 +45,7 @@ public sealed class GetIshUser : UserCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "MyMetadataGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -54,7 +54,7 @@ public sealed class GetIshUser : UserCmdlet /// <para type="description">The activity filter to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public Enumerations.ActivityFilter ActivityFilter { @@ -73,7 +73,7 @@ public Enumerations.ActivityFilter ActivityFilter /// <para type="description">The metadata filter with the filter fields to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] MetadataFilter { get; set; } @@ -81,7 +81,7 @@ public Enumerations.ActivityFilter ActivityFilter /// <para type="description">The metadata fields to retrieve</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "MyMetadataGroup")] [ValidateNotNull] public IshField[] RequestedMetadata { get; set; } @@ -89,7 +89,7 @@ public Enumerations.ActivityFilter ActivityFilter /// <summary> /// <para type="description">Users for which to retrieve the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/SetIshUser.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/SetIshUser.cs index e1f9b85..14efc36 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/SetIshUser.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/User/SetIshUser.cs @@ -47,7 +47,7 @@ public sealed class SetIshUser : UserCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -66,7 +66,7 @@ public sealed class SetIshUser : UserCmdlet /// <summary> /// <para type="description">Users for which to update the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/AddIshUserGroup.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/AddIshUserGroup.Tests.ps1 index 715691e..4f19578 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/AddIshUserGroup.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/AddIshUserGroup.Tests.ps1 @@ -51,7 +51,7 @@ Describe "Add-IshUserGroup" -Tags "Create" { $ishSession.StrictMetadataPreference = $strictMetadataPreference } } - Context "Add-IshUserGroup IshObjectsGroup" { + Context "Add-IshUserGroup IshObjectGroup" { BeforeAll { $userGroupName = ($cmdletName + " " + (Get-Date -Format "yyyyMMddHHmmssfff") + " A") $ishObjectA = Add-IshUserGroup -IshSession $ishSession -Name $userGroupName diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/AddIshUserGroup.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/AddIshUserGroup.cs index b2757e3..7a80c44 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/AddIshUserGroup.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/AddIshUserGroup.cs @@ -45,14 +45,14 @@ public sealed class AddIshUserGroup : UserGroupCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } /// <summary> /// <para type="description">Array with the user groups to create. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/GetIshUserGroup.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/GetIshUserGroup.cs index 8be3eeb..38d9e75 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/GetIshUserGroup.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/GetIshUserGroup.cs @@ -45,7 +45,7 @@ public sealed class GetIshUserGroup : UserGroupCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -53,7 +53,7 @@ public sealed class GetIshUserGroup : UserGroupCmdlet /// <para type="description">The activity filter to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public Enumerations.ActivityFilter ActivityFilter { @@ -71,7 +71,7 @@ public Enumerations.ActivityFilter ActivityFilter /// <para type="description">The metadata filter with the filter fields to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] MetadataFilter { get; set; } @@ -79,14 +79,14 @@ public Enumerations.ActivityFilter ActivityFilter /// <para type="description">The metadata fields to retrieve</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] RequestedMetadata { get; set; } /// <summary> /// <para type="description">Array with the user groups for which to retrieve the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/RemoveIshUserGroup.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/RemoveIshUserGroup.cs index dfbaaaa..6ce7c4f 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/RemoveIshUserGroup.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/RemoveIshUserGroup.cs @@ -42,7 +42,7 @@ public sealed class RemoveIshUserGroup : UserGroupCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -55,7 +55,7 @@ public sealed class RemoveIshUserGroup : UserGroupCmdlet /// <summary> /// <para type="description">Array with the user groups to remove. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/SetIshUserGroup.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/SetIshUserGroup.cs index 16a8649..2b873a1 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/SetIshUserGroup.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserGroup/SetIshUserGroup.cs @@ -47,7 +47,7 @@ public sealed class SetIshUserGroup : UserGroupCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -66,7 +66,7 @@ public sealed class SetIshUserGroup : UserGroupCmdlet /// <summary> /// <para type="description">User groups for which to update the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/AddIshUserRole.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/AddIshUserRole.Tests.ps1 index b323e03..dbed2d0 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/AddIshUserRole.Tests.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/AddIshUserRole.Tests.ps1 @@ -51,7 +51,7 @@ Describe "Add-IshUserRole" -Tags "Create" { $ishSession.StrictMetadataPreference = $strictMetadataPreference } } - Context "Add-IshUserRole IshObjectsGroup" { + Context "Add-IshUserRole IshObjectGroup" { BeforeAll { $userRoleName = ($cmdletName + " " + (Get-Date -Format "yyyyMMddHHmmssfff") + " A") $ishObjectA = Add-IshUserRole -IshSession $ishSession -Name $userRoleName diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/AddIshUserRole.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/AddIshUserRole.cs index 309e8be..a09c034 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/AddIshUserRole.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/AddIshUserRole.cs @@ -45,14 +45,14 @@ public sealed class AddIshUserRole : UserRoleCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } /// <summary> /// <para type="description">Array with the user roles to create. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/GetIshUserRole.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/GetIshUserRole.cs index 7d7e020..ea7b410 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/GetIshUserRole.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/GetIshUserRole.cs @@ -45,7 +45,7 @@ public sealed class GetIshUserRole : UserRoleCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -53,7 +53,7 @@ public sealed class GetIshUserRole : UserRoleCmdlet /// <para type="description">The activity filter to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public Enumerations.ActivityFilter ActivityFilter { @@ -71,7 +71,7 @@ public Enumerations.ActivityFilter ActivityFilter /// <para type="description">The metadata filter with the filter fields to limit the amount of objects returned. Default is no filtering.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] MetadataFilter { get; set; } @@ -79,14 +79,14 @@ public Enumerations.ActivityFilter ActivityFilter /// <para type="description">The metadata fields to retrieve</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNull] public IshField[] RequestedMetadata { get; set; } /// <summary> /// <para type="description">Array with the user roles for which to retrieve the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/RemoveIshUserRole.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/RemoveIshUserRole.cs index 288d680..0ab71b0 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/RemoveIshUserRole.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/RemoveIshUserRole.cs @@ -42,7 +42,7 @@ public sealed class RemoveIshUserRole : UserRoleCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -55,7 +55,7 @@ public sealed class RemoveIshUserRole : UserRoleCmdlet /// <summary> /// <para type="description">Array with the user roles to remove. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/SetIshUserRole.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/SetIshUserRole.cs index 54160d7..be063bf 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/SetIshUserRole.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/UserRole/SetIshUserRole.cs @@ -47,7 +47,7 @@ public sealed class SetIshUserRole : UserRoleCmdlet /// <para type="description">The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet.</para> /// </summary> [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "ParameterGroup")] - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] [ValidateNotNullOrEmpty] public IshSession IshSession { get; set; } @@ -66,7 +66,7 @@ public sealed class SetIshUserRole : UserRoleCmdlet /// <summary> /// <para type="description">User roles for which to update the metadata. This array can be passed through the pipeline or explicitly passed via the parameter.</para> /// </summary> - [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectsGroup")] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] [AllowEmptyCollection] public IshObject[] IshObject { get; set; } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/_TestEnvironment/TestAnonymization.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/_TestEnvironment/TestAnonymization.Tests.ps1 new file mode 100644 index 0000000..7c1bb78 --- /dev/null +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/_TestEnvironment/TestAnonymization.Tests.ps1 @@ -0,0 +1,135 @@ +#Requires -Version 5.1 +<# + TestAnonymization.Tests.ps1 + + Repo-hygiene meta-test: asserts the public, blog-like Doc/ and Samples/ material uses + example.com placeholders instead of real server URLs, internal hostnames, private IPs or + tokens (see .github/instructions/doc--markdown.instructions.md and + source-samples--mixed.instructions.md). + + This test scans static files only - it needs no IshSession and no live server - so it + deliberately does NOT dot-source ISHRemote.PesterSetup.ps1. The few acknowledged historical, + non-routable references from the "thinking out loud" notes are baked into $knownException + inside Get-IshContentLeak; extend that list only for intentional history. +#> + +Describe 'Public Doc and Samples stay anonymized' { + + BeforeAll { + # Returns one 'L<line>: <match>' entry per leak found in the given lines of text. + function Get-IshContentLeak { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [AllowEmptyString()] + [AllowEmptyCollection()] + [string[]] $Line + ) + Set-StrictMode -Version Latest + + # Acknowledged historical, non-routable references already committed to the notes. + $knownException = @( + 'medevddemeyer10.global.sdl.corp' + 'mecdev14qa01.global.sdl.corp' + 'mecdev12qa01.global.sdl.corp' + '192.168.1.160' + ) + # Hostnames that are obviously placeholders, never a leak. + $placeholderHost = @( + 'localhost', '127.0.0.1', 'hostname', 'host', + 'server', 'servername', 'yourhost', 'yourserver' + ) + # High-signal rules; each optional Filter returns $true only for a real finding. + $rule = @( + @{ + Pattern = 'https?://(?<host>[\w.<>-]+)/(?:ISHWS|ISHCM|ISHSTS|ISHAM|InfoShare)\w*/' + Filter = { + param([System.Text.RegularExpressions.Match] $Match) + $candidateHost = $Match.Groups['host'].Value.ToLowerInvariant() + if ($candidateHost -match '[<>]') { return $false } + if ($candidateHost -eq 'example.com' -or $candidateHost.EndsWith('.example.com')) { return $false } + return -not ($placeholderHost -contains $candidateHost) + } + } + @{ + Pattern = '\b[\w-]+\.(?:global\.)?sdl\.corp\b' + Filter = $null + } + @{ + Pattern = '\b\d{1,3}(?:\.\d{1,3}){3}\b' + Filter = { + param([System.Text.RegularExpressions.Match] $Match) + $octet = $Match.Value -split '\.' + foreach ($part in $octet) { if ([int] $part -gt 255) { return $false } } + ([int] $octet[0] -eq 10) -or + ([int] $octet[0] -eq 192 -and [int] $octet[1] -eq 168) -or + ([int] $octet[0] -eq 172 -and [int] $octet[1] -ge 16 -and [int] $octet[1] -le 31) + } + } + @{ + Pattern = '\beyJ[\w-]{10,}\.eyJ[\w-]{10,}\.[\w-]+' + Filter = $null + } + ) + + $leak = [System.Collections.Generic.List[string]]::new() + for ($index = 0; $index -lt $Line.Count; $index++) { + foreach ($currentRule in $rule) { + foreach ($match in [regex]::Matches($Line[$index], $currentRule.Pattern)) { + if ($null -ne $currentRule.Filter -and -not (& $currentRule.Filter $match)) { continue } + $value = $match.Value + if ($knownException | Where-Object { $value.Contains($_) }) { continue } + $leak.Add(('L{0}: {1}' -f ($index + 1), $value)) + } + } + } + , $leak.ToArray() + } + + # Scans every text file under Doc/ and Samples/, returning '<relative path> L<line>: <match>'. + function Get-IshAnonymizationFinding { + [CmdletBinding()] + param() + Set-StrictMode -Version Latest + + # This file lives at Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/_TestEnvironment/, + # so the repository root is five folders up. + $repoRoot = (Resolve-Path -LiteralPath (Join-Path -Path $PSScriptRoot -ChildPath '..\..\..\..\..')).Path + $textExtension = '.md', '.markdown', '.txt', '.ps1', '.psm1', '.psd1', '.xml', '.json', + '.yml', '.yaml', '.html', '.htm', '.xhtml', '.dita', '.ditamap', '.csv', '.config' + $scanRoot = @( + (Join-Path -Path $repoRoot -ChildPath 'Doc') + (Join-Path -Path $repoRoot -ChildPath 'Source\ISHRemote\Trisoft.ISHRemote\Samples') + ) + + $finding = [System.Collections.Generic.List[string]]::new() + foreach ($root in $scanRoot) { + if (-not (Test-Path -LiteralPath $root)) { continue } + $file = Get-ChildItem -LiteralPath $root -Recurse -File | + Where-Object { $textExtension -contains $_.Extension.ToLowerInvariant() } + foreach ($currentFile in $file) { + $relPath = ($currentFile.FullName.Substring($repoRoot.Length).TrimStart('\', '/') -replace '\\', '/') + foreach ($leak in (Get-IshContentLeak -Line @(Get-Content -LiteralPath $currentFile.FullName))) { + $finding.Add("$relPath $leak") + } + } + } + , $finding.ToArray() + } + } + + It 'rules flag a non-anonymized sample (guard-for-the-guard)' { + Get-IshContentLeak -Line 'connect to https://realhost.acme.local/ISHWS/ now' | Should -Not -BeNullOrEmpty + Get-IshContentLeak -Line 'internal box at 10.20.30.40' | Should -Not -BeNullOrEmpty + } + + It 'rules ignore the agreed example.com placeholders' { + Get-IshContentLeak -Line 'New-IshSession -WsBaseUrl https://ish.example.com/ISHWS/' | Should -BeNullOrEmpty + Get-IshContentLeak -Line 'Generate "https://hostname/InfoShareWS/" "username" "password"' | Should -BeNullOrEmpty + } + + It 'Doc and Samples contain no real URL, internal hostname, private IP or token' { + $finding = Get-IshAnonymizationFinding + $finding | Should -BeNullOrEmpty -Because 'public material must use example.com placeholders (see .github/instructions/doc--markdown.instructions.md)' + } +} diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareOpenIdConnectSystemBrowser.cs b/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareOpenIdConnectSystemBrowser.cs index 2245602..af6db32 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareOpenIdConnectSystemBrowser.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareOpenIdConnectSystemBrowser.cs @@ -103,31 +103,46 @@ public async Task<BrowserResult> InvokeAsync(BrowserOptions options, Cancellatio public void OpenBrowser(string url) { _logger.WriteDebug($"InfoShareOpenIdConnectSystemBrowser OpenBrowser url[{url}]"); + // NOTE: this method is only ever called with options.StartUrl — the IDP authorization + // endpoint URL. The OIDC redirect callback (http://127.0.0.1:PORT/?code=...) is + // returned by listener.WaitForCallbackAsync() and never reaches here. + // + // Validate that url is a well-formed http/https URI before passing to Process.Start. + // Breaks taint flow for CodeQL cs/uncontrolled-process-creation (CWE-78) by rejecting + // dangerous schemes (javascript:, file:, cmd:, ms-settings:, etc.). Both http and https + // are allowed so that internal/dev IDPs without TLS (IgnoreSslPolicyErrors scenarios) + // continue to work. The re-serialised validatedUri.AbsoluteUri is used throughout so + // the tainted raw string never reaches any process-creation call. + if (!Uri.TryCreate(url, UriKind.Absolute, out Uri validatedUri) || + (validatedUri.Scheme != Uri.UriSchemeHttps && validatedUri.Scheme != Uri.UriSchemeHttp)) + { + throw new ArgumentException($"InfoShareOpenIdConnectSystemBrowser requires a well-formed http or https url[{url}]"); + } try { - Process.Start(url); + Process.Start(validatedUri.AbsoluteUri); } catch { // Optimized to bypass issue https://github.com/dotnet/corefx/issues/10361 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - //url = url.Replace("&", "^&"); - //Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true }); + //validatedUri.AbsoluteUri = validatedUri.AbsoluteUri.Replace("&", "^&"); + //Process.Start(new ProcessStartInfo("cmd", $"/c start {validatedUri.AbsoluteUri}") { CreateNoWindow = true }); ProcessStartInfo processStartInfo = new ProcessStartInfo { - FileName = url, + FileName = validatedUri.AbsoluteUri, UseShellExecute = true }; Process.Start(processStartInfo); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - Process.Start("xdg-open", url); + Process.Start("xdg-open", validatedUri.AbsoluteUri); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - Process.Start("open", url); + Process.Start("open", validatedUri.AbsoluteUri); } else { diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpInstructions.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpInstructions.ps1 index f4aaed4..8d107ea 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpInstructions.ps1 +++ b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpInstructions.ps1 @@ -1,151 +1,14 @@ <# +.SYNOPSIS +Returns the ISHRemote MCP server instructions as a compressed JSON string. .DESCRIPTION - Below are LLM instructions for using the IShRemoteMcpServer and its tools. - These instructions are meant to be read by humans, but are also available as an MCP Resource for LLMs to understand - how to use the IShRemoteMcpServer effectively. +Reads Doc/McpInstructions-ISHRemote.md, which is copied next to this script during the +build, and returns the full Markdown content as a compressed JSON string for consumption +by the MCP server protocol. The source file in Doc/ is the single source of truth for +Tridion Docs domain knowledge shared between humans, agents, and the MCP runtime. #> function Register-IshRemoteMcpInstructions { - $instructions= -@" - -# Introduction - -IShRemoteMcpServer is a Model Context Provider implementation on top of PowerShell module IShRemote. The IShRemote module, and in turn all supporting PowerShell constructs, can be used to define an answer. Every IShRemote cmdlet has been made available as an MCP Tool to use; where the parameters of the cmdlet became the parameters of the MCP Tool. - -The IShRemote module is used by many clients in PowerShell PS1 script files to achieve business automation on top of a Tridion Docs CMS (Content Management System). Such a Tridion Docs environment, often simplified to server, is typically identified by its `-WsBaseUrl "https://example.com/ISHWS/"` or variation. - - -# Getting Started - -To execute these MCP Tools, so IShRemote cmdlets, you need to create a `IshSession`. If not you will see MCP Tools responding with `IshSession is null. Please create a session first using New-IshSession`. The cmdlet `New-IShSession` with provided `-WsBaseUrl` https url is triggered first - potenially with `-ClientId` and `-ClientSecret` parameters if provided. The result is an in-memory IShSession object that implicitly set as a variable accessible over `$ISHRemoteSessionStateIshSession`. This means that you never have to explicitly mention parameter `-IShSession` or value `$ishSession` because every cmdlet takes that implicitly from `$ISHRemoteSessionStateIshSession`. - - -# Rules - -1. IShRemote PowerShell cmdlets can be recognized by their naming convention as the noun starts with `ISh`, so verb-`ISh`noun like `New-IShSession`. Most of the cmdlets of IShRemote are made available as MCP Tools with the same or reduced parameter set - for example parameter `-IshSession` with value `$ishSession` is superfluous and should be avoided. Do not pass parameter `-IshSession` to any MCP Tool. -2. All ISHRemote MCP Tools are allowed to be loaded, use `Get-Help` to help resolve the request. Do not make the user individually activate groups of MCP Tools. -3. Do not make up parameters for the cmdlets! Do not use any JSON parameters! Do not use hash table structures (`@{ Name = "FUSERNAME"; Value = ...}`) for ISHRemote cmdlets! All parameters should exist and can be verified over the MCP Tool description or by triggering ISHRemote MCP Tool `Get-Help` cmdlet on it. -4. Do not make up field names or imagine their field level! You must use `Get-IshTypeFieldDefinition` to understand the available field and object types, including custom fields. You can reuse the `Get-IshTypeFieldDefinition` result until a `New-IShSession` was triggered to save tokens. Do not ask for `Continue` for this `Get-IshTypeFieldDefinition` MCP Tool. -5. Respect the casing of parameters, fields and properties! PowerShell, and in turn ISHRemote, is case-sensitive! -6. The verb of PowerShell cmdlet indicates if it about read or write operations. Write operations like `Set` are about updating an existing CMS entity, `Add` and `Remove` about creating and deleting them. Read operations like `Find` and `Get` retrieve information over the primary relational database and should in turn be used with caution as a `Find` can theoretically return you the full CMS repository leading to out-of-memory. Therefor `Set-IshMetadataFilterField` usage is strongly advised to do server-side filtering as much as possible. Note that the wildcard operator for `like` and `cilike` is percentage (`%`) and not asterix (`*`). The `Search` retrieves information over the secondary full text index for `IshDocumentObj` and is considered faster than the `Find`. -7. Read operations, typically starting with `Get`, `Find` or `Search` can be executed by you to verify the returned fields and PSNoteProperty properties. Always verify the outcome of read operations (like `Find-IshOutputFormat`) before attempting any write operations (like `Set-IshOutputFormat`). This ensures you are targeting the correct entity and prevents accidental changes. -8. All IShRemote cmdlets come with preformatted tables, there is no need to pipeline (`|`) these results in a `Format-Table` cmdlet. -9. You can always do a `Get-Help` for any ISHRemote cmdlet with option `-Detailed` so you get insights in its purpose, parameter sets which are allowed parameter combinations, the individual parameters explained and of course several typical examples. Do not ask for `Continue` for this `Get-Help` MCP Tool. -10. `Get-IShUser` without parameters is the shallow `whoami` call that checks if you are still authenticated. If not do a `New-IShession` again. - - -# Concepts - - -## Entities Hierarchy - -Essentially the Tridion Docs product, often called InfoShare, uses the following entity types inside its database. -- Fields are in essence key value pairs, where the key is often prefixed with `F` short for field and `FISH` for product system fields like examples `FAUTHOR` or `FISHLINKS`. The value can be - - `String` (`STRING`), where a string field is advised to be lower than 1333 characters. Its database storage allows rich filtering over `like` (case-sensitive) and `cilike` (case-insensitive) operators. Important to note that the wildcard operator for `like` and `cilike` is percentage (`%`) and not asterix (`*`). - - `LongText` (`LONG TEXT`), is also a string field where the value can be over 1333 characters. However, the operators to filter are limited to `empty` or `notempty` - - `ISHMetadataBinding`, is also a string field but holds identifiers from the remotely linked taxonomy system. - - `Number` (`NUMBER`), holds digits potentially with decimal separator. Many operators are available as listed under section `FilterOperator`. - - `DateTime` (`DATE`), holds a date and time representation. Many operators are available as listed under section `FilterOperator`. - - `ISHLov` (`LOV`), short for List-of-Values or sometimes called domain or permitted values, is a controlled list of allowed values. This enumeration has values identified by a `label` and an idempotent `element` identifier. The `ValueType` `label` as created through cmdlet `Add-IshLovValue` is allowed to change afterwards over cmdlet `Set-IshLovValue`, hence not advised to use in API calls. The `ValueType` `element`, once created is stable and outlives label renames. - - `ISHType` (`CARD`), similar to List-of-Values, however this time the permitted values are of the type of the linked card type. Example is `IShUser` to indentity that a field like `FAUTHOR` links to an existing user instance. Same for an `IShFolder` that needs to link over `FUSERGROUP` to an existing `IShUserGroup`. -- A CARD like system, where CARD instance holds fields with values of the type LOV, DATE, NUMBER, STRING or LONG TEXT. -- A single CARD instance holding fields is identified by `Level` `none`. This level is implicit as there is no confusion on which field to address on this card instance. Examples of this type are `IShUser`, `IShUserGroup`, `IShUserRole`, `IShConfiguration`, `IShOutputFormat`, `IShEDT` or `IShTranslationJob`. The typical identifier (property `IShRef`), is a generated somewhat readable identifier. For example `VUSERADMIN` which is prefix `V` for value, then `USER` for the object type and `ADMIN` is a proprietary unicode encoding of the label provided at creation type. -- A multi CARD instance holding fields is identified by its `Level`. As there is potential confusion of having the same field name on multiple levels, like `MODIFIED-ON` (which holds the last modified date of that card level instance), it is advised to be explicit in specifying levels. The typical identifier (property `IShRef`), is a Global Unique Identifier (GUID), for the top entity like `logical`. - - `IShPublication`, `IShDocumentObj` and their derived types use three levels: `logical`, `version` and `lng` (language). - - `IShAnnotation` uses levels: `Annotation` or `Reply` - - `ISHProject` uses levels: `Project` and `ProjectAssignee` -- There is also a TABLE like system where the columns of the tables are expressed as field names and the alias names of the table are expressed as levels. - - `IShBaseline` has a TABLE subsystem known as `IShBaselineItem` - - `IShTranslationJob` has a TABLE subsystem known as `IShTranslationJobItem` - - `IShEvent` has a TABLE subsystem with Level `Progress` and `Detail` - - `IShBackgroundTask` has a TABLE subsystem with Level `Task` and `History` - -These base entities surface over the business and api code to the ISHRemote library as the below Field Types and Object Types which can be interacted with. - - -## Field Types - -There is a hierarchy where ISHRemote base class `IShField`, that represents the field `Name` and `Level`, is extended to -- `IShRequestedMetadataField` where `Name` and `Level` is reused and enriched with `ValueType` which highlights by `Value` the typical label of an entity or by `Element` the idempotent identifier of an entity. -- `IShMetadataField` where `Name` and `Level` is reused and enriched with `ValueType` which highlights by `Value` the typical label of an entity or by `Element` the idempotent identifier of an entity. Another property added here is `Value` which represent the value of the metadata field. -- `IShMetadataFilterField` where `Name` and `Level` is reused and enriched with `ValueType` which highlights by `Value` the typical label of an entity or by `Element` the idempotent identifier of an entity. Another property added here is `Value` which represent the value of the metadatafield. The last property is `FilterOperator` which an enumeration of allowed API operators. - -## Object Types - -Most object types are simple classes to return values like `IShSession` or `IShTypeFieldDefinition`. - -There is a hierarchy where base class `IShObject` is used for PowerShell pipeline actions, either as input or output for a cmdlet. Derived classes are known as `ISHType` as listed - - `IShAnnotation` holds an instance with fields (`IShField`) of level `Annotation` or `Reply` that refer to comments, annotations and suggestions. - - `IShBaseline` holds an instance with fields (`IShField`) of level `None` and can be linked to `IShBaselineItem` entries that list exact versions per logical identifier. - - `IShConfiguration` holds the singleton instance with fields (`IShField`) of level `None` that holds feature toggles, Xml Settings and more system configuration. - - `IShEDT` holds an instance with fields (`IShField`) of level `None`. EDT is short for Electronic Document Type that links a typical file extension and mimetype of some binary. Well known EDT is `EDTXML` for xml files and `EDTPNG` for png files. This information is also offered when downloading Data (`IShData`). - - `IShFolder` holds an instance with fields (`IShField`) of level `None`. Folders or sometimes called Directories are nested structures to organize `IShPublication` and `IShDocumentObj` derived entities. Depth 1 returns the current folder, depth 2 first level of subfolders and typically requires the recurse parameter. - - `IShDocumentObj`, also known as content objects. A denormalized structure of the `logical`, `version` and `lng` (language) level fields (`IShField`). - - `IShIllustration` in turn is derived from `IShDocumentObj` and holds content objects known as images, illustrations, graphics. A unique language level identifier for this type is lng level field resolution (`FRESOLUTION`) which indicates the purpose of an image - for example `Low` for web resolution, `High` for print quality and `Master` could be the source vector graphic that generated the earlier two. - - `IShLibrary` in turn is derived from `IShDocumentObj` and holds content objects known as library topics, conref libraries or variable libraries. Typically holding OASIS DITA topic xml files of type `EDTXML`. - - `IShMasterDoc` in turn is derived from `IShDocumentObj` and holds content objects known as master documents, maps, bookmaps. Typically holding OASIS DITA map xml files of type `EDTXML`. - - `IShModule` in turn is derived from `IShDocumentObj` and holds content objects known as topics, concepts, tasks or reference topics. Typically holding OASIS DITA topic xml files of type `EDTXML`. - - `IShTemplate` in turn is derived from `IShDocumentObj` and holds content objects known as templates of type 'Other'. Not to be confused with 'Editor Templates'. Typically holding the various binary files for supporting resources that need storing in the CMS. - - `IShOutputFormat` holds an instance with fields (`IShField`) of level `None`. Output formats is a controlled list of rendering outputs indicating a certain layout or format. Typical examples are 'PDF A4' (so PDF files of page size A4) or 'Dynamic Delivery' (so downstream DXD or Genius web delivery format). Overall the output format is a key in identifying the language level of publication. - - `IShProject` holds an instance with fields (`IShField`) of level `Project` and `ProjectAssignee`. A project is an entity in the publication hub to identify active publications to users. - - `IShPublication`, also known as publication output. A denormalized structure of the `logical`, `version` and `lng` (language) level fields (`IShField`). The language level is commenly known as publication output as it uniquely identifies the logical, version and language level of a publication. This all the way to the selected output format. - - `IShTranslationJob` holds an instance with fields (`IShField`) of level `None` and can be linked to `IShTranslationJobItem` entries that list exact versions per logical identifier. - - `IShUser` holds an instance with fields (`IShField`) of level `None`. A user is the entity used to assign authoring (`FAUTHOR`) or reviewing (`FREVIEWER`) work to. When creating a `New-IShSession` you will be authenticated to a user instance, which in turn gives you authorization for CMS actions. - - `IShUserGroup` holds an instance with fields (`IShField`) of level `None`. A user group is assigned to protect `IShFolder` with read and write permission. This read and write security is saved on all `IShPublication` and `IShDocumentObj` derived entities that are organized in those folders. In turn an `IShUser` needs user groups assigned to be able to read or update folders or entities in those folders. - - `IShUserRole` holds an instance with fields (`IShField`) of level `None`. A user role is assigned to an `IShUser` to authorize the user for activities in the CMS. The power user role is called `Administrator` and can be considered CMS root access. User Roles can have one or more priviliges, also known as permissions, assigned to enable users specific actions. - -## Requesting metadata, Get-IshTypeFieldDefinition and Set-IshRequestedMetadataField - -ISHRemote offers defaults for the optional `-RequestedMetadata` parameter that many cmdlets, so MCP Tools, have. The default is calculated as taking the `Get-IshTypeFieldDefinition` for the current Object Type. So cmdlets returning data like reading `Find-IShUser` or even creating `Add-IShUser` which returns the created instance will filter the table on column `ISHType` to `IShUser` and `SDB` column needs to hold `B`. - -The `Get-IshTypeFieldDefinition` returns the object types, field types and more in a table format. The columns should be interpreted as follows. -* `ISHType` column as explained under object types like `IShIllustration`, `IShFolder`, `IShUser` and more. -* `Level` column as explained under field types indicates if the mentioned field is linked to a single (`None`) or hierarchy level. -* `Name` holds the name of a standard or custom field like `FAUTHOR`. -* `DataType` column as explained under entity types like `String`, `LongText`, `ISHLov`, `ISHType`, etc -* `DataSource` column only holds a value for `DataType` entries `ISHLov`, `ISHType` and `ISHMetadataBinding`. When `ISHLov` the data source entry holds the list-of-values name like `DSTATUS` which can be further queried over `Get-IshLovValue` cmdlets. When `ISHType` the data source entry holds the object type that this object links to like field `FUSERGROUP` on `ISHType` `ISHFolder` will hold an `ISHUserGroup` reference or `FAUTHOR` on `ISHIllustration` will hold an `ISHUser` reference. When `ISHMetadataBinding` the data source holds an information setting value, the only important piece is that this field can only filtered using the `-ValueType` `Element` option. -* `SDB` column will indicate `B` for Basic, and `D` for Descriptive (so only object identifiers) and `S` for product system fields. When the field is not identified as one of these a hyphen `-` will appear. For example field `FSTATUS` is identified as `S-B` so a System field, not a Descriptive field and considered Basic field to work with this `ISHType`. -* `MM` column where the first letter is either `M` indicating mandatory or hyphen `-` indicating not. The second letter is either `1` indicating that the field can only hold a single value, or `n` indicating that the field can hold multiple values. These multiple values are typical when a `DataSource` is used to store multiple selections. -* `CRUST` column where the first letter either is `C` or hyphen `-` indicating that the field can be passed on an `Add` cmdlet while creating it. Second letter `R` or hyphen `-` indicating that the field can be passed as requested metadata - for example passwords fields cannot be read. Third letter `U` or hyphen `-` indicating that the field can be passed as metadata to update the entity. Fourth letter `S` or hyphen `-` indicates if the field is searchable in the full text index. Fifth letter `T` or hyphen `-` indicating if the field is SmartTagging enabled over `ISHMetadataBinding. -* `Description` holds an informational description of the named field. - -## Filtering metadata, FilterOperator and Set-IshMetadataFilterField - -Below table is replicated from public documentation [Understanding metadata filter operators](https://docs.rws.com/en-US/tridion-docs-main-documentation-1165616/understanding-metadata-filter-operators-68174). The first column holds the allowed filter operators of the `-FilterOperator` parameter that is used on the `Set-IshMetadataFilterField` cmdlet. - -| | ISHType | ISHLov | DateTime | Number | String | LongText | -| --- | --- | --- | --- | --- | --- | --- | -| `equal` | X | X | X | X | X | | -| `not equal` | X | X | | X | X | | -| `in` | X | X | X | X | X | | -| `not in` | X | X | | X | X | | -| `like` | X | X | | | X | | -| `cilike` | | | | | X | | -| `greater than` | | | X | X | | | -| `less than` | | | X | X | | | -| `greater than or equal` | | | X | X | | | -| `less than or equal` | | | X | X | | | -| `between` | | | X | X | | | -| `empty` | X | X | X | X | X | X | -| `not empty` | X | X | X | X | X | X | - -Where `ISHMetadataBinding` typically is a multi-value `String` field holding the remote taxonomy identifiers (`element`), the CMS does not store the `label`s as they are retrieved live from the remote taxonomy. - -## Updating metadata and Set-IshMetadataField - -All Object Types mentioned as output of a cmdlet are client side in-memory representations. If you would update the fields of this object using `Set-IshMetadataField`, then you updated the client side in-memory representation, it would still require the matching `Set` operation (like object `IshUser` requires `Set-IshUser`). - -## Reading metadata, using PSNoteType properties - -The returned in-memory objects hold the requested implicit or explicitly requested metadata over parameter `-RequestedMetadata`. To access the returned fields `IShField` of a card instance, one would historically use cmdlet `Get-IshMetadataField`. A much more convenient system is to use the generated read-only PSNoteType properties which also provides the dates in sortable ISO8601 format. - -To predict the naming of PSNoteType properties you can use a quick `Format-List` on the returned in-memory object like `$ishObject | Format-List`. This will show the lower-cased and underscore seperated field name, field level and value type. -* The commonly used field levels `None`, `Task`, `Progress` and `Lng` are omitted. The same goes for value type `value` which is also omitted. As an example field `FISHUSERTYPE` on `IShUser` will be offered as read-only property `fishusertype` holding the human-readable value `Internal`. -* When there is doubt the full level and value type is used in lower-case format. To continue the example the read-only property `fishusertype_none_element` holding the stable identifier `VUSERTYPEINTERNAL` (so a `V` list of value entry of `DUSERTYPE´). - -Note these PSNoteType properties are read-only, they can only be used to read or process values. Any change like updating metadata needs to go over `Set-IshMetadataField`. -"@ - - + $instructionsFilePath = Join-Path $PSScriptRoot 'McpInstructions-ISHRemote.md' + $instructions = Get-Content -Path $instructionsFilePath -Raw Write-Output ($instructions | ConvertTo-Json -Compress) } \ No newline at end of file diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Trisoft.ISHRemote.csproj b/Source/ISHRemote/Trisoft.ISHRemote/Trisoft.ISHRemote.csproj index 649d2e0..ad0a4aa 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Trisoft.ISHRemote.csproj +++ b/Source/ISHRemote/Trisoft.ISHRemote/Trisoft.ISHRemote.csproj @@ -191,6 +191,7 @@ <MakeDir Directories="$(ISHRemoteFrameworkDir)" Condition="!Exists('$(ISHRemoteFrameworkDir)')" /> <Copy SourceFiles="@(ISHRemoteFiles)" DestinationFiles="@(ISHRemoteFiles->'$(ISHRemoteFrameworkDir)\%(RecursiveDir)%(Filename)%(Extension)')" /><!-- hyphen-gt arrow needs to remain entity gt --> <Copy SourceFiles="@(ISHRemoteScriptFiles)" DestinationFiles="@(ISHRemoteScriptFiles->'$(LocalPSRepoDir)\ISHRemote\Scripts\%(RecursiveDir)%(Filename)%(Extension)')" /><!-- hyphen-gt arrow needs to remain entity gt --> + <Copy SourceFiles="$(ProjectDir)..\..\..\Doc\McpInstructions-ISHRemote.md" DestinationFolder="$(LocalPSRepoDir)\ISHRemote\Scripts\Public\" /> <Copy SourceFiles="$(ProjectDir)ISHRemote.psm1" DestinationFolder="$(ProjectDir)bin/$(Configuration)/ISHRemote" /> <Copy SourceFiles="$(ProjectDir)ISHRemote.Format.ps1xml" DestinationFolder="$(ProjectDir)bin/$(Configuration)/ISHRemote" /> <Delete Files="@(ISHRemoteTestFilesToDelete)" />