You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Large Tridion Docs repositories accumulate publication outputs over years of use. The bulk of that storage is rendered blobs attached to released publication outputs (PDF, HTML packages, etc.) that are no longer needed for daily authoring work or stored outside of the system for save keeping. There is no product-native archiving mechanism, and restructuring the database at the server level requires product intervention, upgrades, and downtime.
Clear-IshPublicationOutput addresses this entirely from the client side, working over the existing Web Services API and requiring no product change. Because ISHRemote runs against all supported Tridion Docs versions, the cmdlet is available to any customer.
The cmdlet receives IshPublicationOutput objects from the pipeline and for each logical group:
Retrieves all server-side lng cards for that logical (one RetrieveMetadata call per logical) to resolve the protection heuristic — regardless of whether the piped set is a full or partial subset
Skips outputs currently publishing (VPUBSTATUSPUBLISHING) with a warning
When -Keep FirstCreatedPublicationOutputInVersion: protects the lng card with the lowest ishlngref (first created on the server) per version — this preserves the version metadata container (title, baseline, context document) for future republishing, even if that card was not piped
Unreleases any VPUBSTATUSRELEASED outputs to VPUBSTATUSPUBLISHINGCANCELLED
Removes the remaining candidates via DeleteByIshLngRef; cascades to delete orphaned version and logical cards
Supports -WhatIf and -Confirm throughout.
How to use
Typical pattern — dry run first, then commit
New-IshSession-WsBaseUrl https://example.com/ISHWS/$metadataFilter=Set-IshMetadataFilterField-Name FISHLASTMODIFIEDON -Level Lng
-ValueType Value -FilterOperator LessThan -Value '01/01/2022 00:00:00'# Dry run — see what would be clearedGet-IshFolder-FolderPath 'General/MyPublications'-FolderTypeFilter @(ISHPublication) -Recurse |Get-IshFolderContent-VersionFilter ''-MetadataFilter $metadataFilter|Clear-IshPublicationOutput-WhatIf
# Commit — keep one sentinel lng card per version for republishing (default -Keep value)Get-IshFolder-FolderPath 'General/MyPublications'-FolderTypeFilter @(ISHPublication) -Recurse |Get-IshFolderContent-VersionFilter ''-MetadataFilter $metadataFilter|Clear-IshPublicationOutput-Keep FirstCreatedPublicationOutputInVersion #default# Full removal — no sentinel keptGet-IshFolder-FolderPath 'General/MyPublications'-FolderTypeFilter @(ISHPublication) -Recurse |Get-IshFolderContent-VersionFilter ''-MetadataFilter $metadataFilter|Clear-IshPublicationOutput-Keep None
Clustering assumption:Get-IshFolderContent naturally returns objects grouped by logical id. The cmdlet processes one logical group at a time (low memory, progress-friendly). If the caller pipes objects in a non-clustered order (same logical id appearing after a different one), that logical is processed as two separate groups — the protection logic still works correctly but incurs an extra RetrieveMetadata call.
Currently-publishing outputs are skipped: outputs in VPUBSTATUSPUBLISHING status emit a warning and are not removed. Re-run after publishing completes.
-Keep resolves the protected lng card per version is determined from all server-side lng cards, not just the piped subset. Piping only fr when en, de, fr all exist will still correctly protect the lowest-ishlngref card among all three.
Unrelease requires 14SP4 with CRQ-31475 hotfix, or 15.0.0+. Setting FISHPUBSTATUS to VPUBSTATUSPUBLISHINGCANCELLED via SetMetadataByIshLngRef was made available with that fix. On older servers the unrelease step will throw.
Tasks
Implementation
Add ClearPublicationOutputKeep enum to Objects/Enumerations.cs with values None and FirstCreatedPublicationOutputInVersion; add XML doc comments for future candidate values FirstCreatedPublicationOutputInLatestVersion and LastPublishedPublicationOutputInVersion (not implemented)
Create Cmdlets/PublicationOutput/ClearIshPublicationOutput.cs — sealed class inheriting PublicationOutputCmdlet, [Cmdlet(VerbsCommon.Clear, IshPublicationOutput, SupportsShouldProcess = true)], no [OutputType]
Parameters: IshSession (optional, IshObjectGroup set), IshObject[] (mandatory, ValueFromPipeline, IshObjectGroup set), Keep of type ClearPublicationOutputKeep (optional, default FirstCreatedPublicationOutputInVersion)
BeginProcessing(): standard session resolution verbatim from siblings; no PlatformNotSupportedException guard needed
ProcessRecord(): cluster-detect by IshRef; call ClearLogicalGroup() on logical-id change; accumulate current logical in _logicalBuffer; handles IshObject array arriving per pipeline item via foreach inner loop
EndProcessing(): flush final _logicalBuffer via ClearLogicalGroup(); standard exception ladder
Step 1: RetrieveMetadata for buffer[0].IshRef, ISHNoStatusFilter, requesting FISHPUBSTATUS (Lng/Element) and VERSION (Version/Value) — single logical id, no MetadataBatchSize loop needed
Step 2: if Keep == FirstCreatedPublicationOutputInVersion: build protectedLngRefs (HashSet<long>) from min ObjectRef[Lng] per version across all server-side cards; WriteDebug one line per protected card: LogicalId[...] Version[...] LngRef[...] LngCombination[...] OutputFormat[...]
Step 3: for each piped object in buffer: cross-reference status from server result by lngRef; if VPUBSTATUSPUBLISHING → WriteWarning + skip; if protectedLngRefs.Contains(lngRef) → WriteVerbose + skip; if lngRef not found in server result → throw TrisoftAutomationException; if VPUBSTATUSRELEASED → add to both toUnrelease and toRemove; else → add to toRemove only
Step 5: ShouldProcess + DeleteByIshLngRef per toRemove item; accumulate NameValueCollection for cascade
Step 6: cascade — verbatim from RemoveIshPublicationOutput.cs:174-226 (orphan version cards, then orphan logical cards with <15.1 server version guard)
Step 7: WriteParentProgress(++_logicalsProcessed)
Exception ladder: standard five-catch order verbatim from siblings, wrapping ProcessRecord and EndProcessing bodies
BeforeAll: create ISHPublication folder; add 3 IshPublicationOutput objects for same logical in 3 different language combinations; leave all in draft/VPUBSTATUSPUBLISHPENDING status (no publishing — unrelease path not testable without a full publish cycle)
Test: Keep FirstCreatedPublicationOutputInVersion, only the non-lowest piped → non-lowest removed, lowest untouched on server
Test: Keep FirstCreatedPublicationOutputInVersion, only the lowest piped → nothing removed (lowest is protected)
Test: Keep None, all 3 piped → all 3 removed, logical deleted, Get-IshPublicationOutput returns empty
AfterAll: standard folder-walk cleanup via Remove-IshPublicationOutput -Force
Documentation
Add ReleaseNotes entry for the new cmdlet in Doc/ReleaseNotes-ISHRemote-8.3.md with folder-walk example
Out of scope
REST / OpenAPI implementation — SOAP (WcfSoapWithOpenIdConnect and WcfSoapWithWsTrust) only; REST deferred to a follow-up issue
ParameterGroup overload (-LogicalId/-Version/-OutputFormat/-LanguageCombination) — pipeline-only by design; the caller controls what enters via Get-IshFolderContent or Find-IshPublicationOutput. These parameters would be combination of potential unrelease over Set-IshPublicationOutput followed by Remove-IshPublicationOutput which can already be done.
FirstCreatedPublicationOutputInLatestVersion and LastPublishedPublicationOutputInVersion-Keep enum values — future work documented as comments in Enumerations.cs
Implementation notes
VerbsCommon.Clear is the correct .NET verb constant; Clear-Content (clears file content, keeps the file) is the canonical PS analogy — the container (logical+version metadata shell) survives, the contents (rendered blob + released status) are cleared
ProcessRecord is cluster-aware and processes one logical group at a time via ClearLogicalGroup() — _logicalBuffer holds only the current logical group so memory stays flat across millions of piped objects; do not accumulate everything in EndProcessing
RetrieveMetadata in ClearLogicalGroup takes a single-element string[] for one LogicalId — no MetadataBatchSize batching loop is needed or appropriate here
protectedLngRefs is a HashSet<long> — ishlngref is globally unique so no version key is needed; the minimum per version across all server-side cards is unambiguous
WriteDebug per protected lng card: LogicalId[{logicalId}] Version[{version}] LngRef[{lngRef}] LngCombination[{lngCombination}] OutputFormat[{outputFormat}]
Cascade logic (step 6 in ClearLogicalGroup) is verbatim from RemoveIshPublicationOutput.cs:174-226 — includes the (IshSession.ServerIshVersion.MajorVersion < 15) || (== 15 && MinorVersion < 1) guard for client-side logical card deletion
No [OutputType] attribute — matches RemoveIshPublicationOutput.cs (no output, terminal/destructive cmdlet)
Default -Keep value is FirstCreatedPublicationOutputInVersion (safest for production; caller must explicitly opt out with None)
Business case
Large Tridion Docs repositories accumulate publication outputs over years of use. The bulk of that storage is rendered blobs attached to released publication outputs (PDF, HTML packages, etc.) that are no longer needed for daily authoring work or stored outside of the system for save keeping. There is no product-native archiving mechanism, and restructuring the database at the server level requires product intervention, upgrades, and downtime.
Community thread: https://community.rws.com/product-groups/tridion/tridion-docs/f/forum/61643/shrinking-a-large-test-database-can-you-create-a-new-db-with-all-the-existing-metadata-but-no-data
Clear-IshPublicationOutputaddresses this entirely from the client side, working over the existing Web Services API and requiring no product change. Because ISHRemote runs against all supported Tridion Docs versions, the cmdlet is available to any customer.The cmdlet receives
IshPublicationOutputobjects from the pipeline and for each logical group:RetrieveMetadatacall per logical) to resolve the protection heuristic — regardless of whether the piped set is a full or partial subsetVPUBSTATUSPUBLISHING) with a warning-Keep FirstCreatedPublicationOutputInVersion: protects the lng card with the lowestishlngref(first created on the server) per version — this preserves the version metadata container (title, baseline, context document) for future republishing, even if that card was not pipedVPUBSTATUSRELEASEDoutputs toVPUBSTATUSPUBLISHINGCANCELLEDDeleteByIshLngRef; cascades to delete orphaned version and logical cardsSupports
-WhatIfand-Confirmthroughout.How to use
Typical pattern — dry run first, then commit
Optional: download rendered blobs before clearing
Known limitations
Get-IshFolderContentnaturally returns objects grouped by logical id. The cmdlet processes one logical group at a time (low memory, progress-friendly). If the caller pipes objects in a non-clustered order (same logical id appearing after a different one), that logical is processed as two separate groups — the protection logic still works correctly but incurs an extraRetrieveMetadatacall.VPUBSTATUSPUBLISHINGstatus emit a warning and are not removed. Re-run after publishing completes.-Keepresolves the protected lng card per version is determined from all server-side lng cards, not just the piped subset. Piping onlyfrwhenen,de,frall exist will still correctly protect the lowest-ishlngrefcard among all three.FISHPUBSTATUStoVPUBSTATUSPUBLISHINGCANCELLEDviaSetMetadataByIshLngRefwas made available with that fix. On older servers the unrelease step will throw.Tasks
Implementation
ClearPublicationOutputKeepenum toObjects/Enumerations.cswith valuesNoneandFirstCreatedPublicationOutputInVersion; add XML doc comments for future candidate valuesFirstCreatedPublicationOutputInLatestVersionandLastPublishedPublicationOutputInVersion(not implemented)Cmdlets/PublicationOutput/ClearIshPublicationOutput.cs—sealedclass inheritingPublicationOutputCmdlet,[Cmdlet(VerbsCommon.Clear, IshPublicationOutput, SupportsShouldProcess = true)], no[OutputType]IshSession(optional,IshObjectGroupset),IshObject[](mandatory,ValueFromPipeline,IshObjectGroupset),Keepof typeClearPublicationOutputKeep(optional, defaultFirstCreatedPublicationOutputInVersion)BeginProcessing(): standard session resolution verbatim from siblings; noPlatformNotSupportedExceptionguard neededProcessRecord(): cluster-detect byIshRef; callClearLogicalGroup()on logical-id change; accumulate current logical in_logicalBuffer; handlesIshObjectarray arriving per pipeline item viaforeachinner loopEndProcessing(): flush final_logicalBufferviaClearLogicalGroup(); standard exception ladderClearLogicalGroup(List<IshObject> buffer)private method:RetrieveMetadataforbuffer[0].IshRef,ISHNoStatusFilter, requestingFISHPUBSTATUS(Lng/Element) andVERSION(Version/Value) — single logical id, noMetadataBatchSizeloop neededKeep == FirstCreatedPublicationOutputInVersion: buildprotectedLngRefs(HashSet<long>) from minObjectRef[Lng]per version across all server-side cards;WriteDebugone line per protected card:LogicalId[...] Version[...] LngRef[...] LngCombination[...] OutputFormat[...]VPUBSTATUSPUBLISHING→WriteWarning+ skip; ifprotectedLngRefs.Contains(lngRef)→WriteVerbose+ skip; if lngRef not found in server result → throwTrisoftAutomationException; ifVPUBSTATUSRELEASED→ add to bothtoUnreleaseandtoRemove; else → add totoRemoveonlyShouldProcess+SetMetadataByIshLngRefpertoUnreleaseitem (FISHPUBSTATUS → VPUBSTATUSPUBLISHINGCANCELLED)ShouldProcess+DeleteByIshLngRefpertoRemoveitem; accumulateNameValueCollectionfor cascadeRemoveIshPublicationOutput.cs:174-226(orphan version cards, then orphan logical cards with<15.1server version guard)WriteParentProgress(++_logicalsProcessed)ProcessRecordandEndProcessingbodiesTesting
Cmdlets/PublicationOutput/ClearIshPublicationOutput.Tests.ps1BeforeAll: create ISHPublication folder; add 3IshPublicationOutputobjects for same logical in 3 different language combinations; leave all in draft/VPUBSTATUSPUBLISHPENDINGstatus (no publishing — unrelease path not testable without a full publish cycle)Keep FirstCreatedPublicationOutputInVersion(default), all 3 piped → lowest-ishlngrefsurvives, 2 removedKeep FirstCreatedPublicationOutputInVersion, only the non-lowest piped → non-lowest removed, lowest untouched on serverKeep FirstCreatedPublicationOutputInVersion, only the lowest piped → nothing removed (lowest is protected)Keep None, all 3 piped → all 3 removed, logical deleted,Get-IshPublicationOutputreturns emptyAfterAll: standard folder-walk cleanup viaRemove-IshPublicationOutput -ForceDocumentation
ReleaseNotesentry for the new cmdlet inDoc/ReleaseNotes-ISHRemote-8.3.mdwith folder-walk exampleOut of scope
WcfSoapWithOpenIdConnectandWcfSoapWithWsTrust) only; REST deferred to a follow-up issueParameterGroupoverload (-LogicalId/-Version/-OutputFormat/-LanguageCombination) — pipeline-only by design; the caller controls what enters viaGet-IshFolderContentorFind-IshPublicationOutput. These parameters would be combination of potential unrelease over Set-IshPublicationOutput followed by Remove-IshPublicationOutput which can already be done.FirstCreatedPublicationOutputInLatestVersionandLastPublishedPublicationOutputInVersion-Keepenum values — future work documented as comments inEnumerations.csImplementation notes
VerbsCommon.Clearis the correct .NET verb constant;Clear-Content(clears file content, keeps the file) is the canonical PS analogy — the container (logical+version metadata shell) survives, the contents (rendered blob + released status) are clearedProcessRecordis cluster-aware and processes one logical group at a time viaClearLogicalGroup()—_logicalBufferholds only the current logical group so memory stays flat across millions of piped objects; do not accumulate everything inEndProcessingRetrieveMetadatainClearLogicalGrouptakes a single-elementstring[]for one LogicalId — noMetadataBatchSizebatching loop is needed or appropriate hereprotectedLngRefsis aHashSet<long>—ishlngrefis globally unique so no version key is needed; the minimum per version across all server-side cards is unambiguousWriteDebugper protected lng card:LogicalId[{logicalId}] Version[{version}] LngRef[{lngRef}] LngCombination[{lngCombination}] OutputFormat[{outputFormat}]ClearLogicalGroup) is verbatim fromRemoveIshPublicationOutput.cs:174-226— includes the(IshSession.ServerIshVersion.MajorVersion < 15) || (== 15 && MinorVersion < 1)guard for client-side logical card deletion[OutputType]attribute — matchesRemoveIshPublicationOutput.cs(no output, terminal/destructive cmdlet)-Keepvalue isFirstCreatedPublicationOutputInVersion(safest for production; caller must explicitly opt out withNone)