Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Doc/ReleaseNotes-ISHRemote-8.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The below text describes the delta compared to fielded release ISHRemote v8.1.

Every usage of a cmdlet will refresh the security tokens. However, when not using ISHRemote cmdlets or the implicit local or global `$ISHRemoteSessionStateIshSession` or explicit `$ishSession` object, the session expires by default after around 57 minutes when using ISHID or similar on other identity providers. In turn resulting in error `An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.`.

In this ISHRemote version, the session will attempt to get a new token automatically on every triggererd ISHRemote cmdlet. If you created the IShSession object over an interactive browser, you will see the browser again perhaps with or without a credential challenge in the browser. Change is only for protocols `WcfSoapWithOpenIdConnect` and `OpenApiWithOpenIdConnect`; no change for `WcfSoapWithWsTrust`.
In this ISHRemote version, the session will attempt to get a new token automatically on every triggered ISHRemote cmdlet. If you created the IShSession object over an interactive browser, you will see the browser again perhaps with or without a credential challenge in the browser. Change is only for protocols `WcfSoapWithOpenIdConnect` and `OpenApiWithOpenIdConnect`; no change for `WcfSoapWithWsTrust`.

Infamous random annoying error `The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.` should now recover within the cmdlet or worst-case when rerunning the same cmdlet. This without applying the earlier workaround of building a `New-IshSession`.

Expand All @@ -38,13 +38,13 @@ Model Concept Protocol ([MCP](https://modelcontextprotocol.io/docs/getting-start

Combining documented ISHRemote cmdlets with the interaction pattern of MCP Tools to LLMs, we see at least the below purposes...
1. By offering every ISHRemote cmdlet as an MCP Tool to your LLM, it means you can **ask your system questions** like `Create a new ishsession to https://ish.example.com/ISHWS/`. And yes it will create you that `$ishSession` variable in the background that you can use over the other MCP Tools - or I could say cmdlets. This allows you to use natural language to query your specific instance on `How many user roles are there?` or `which status transitions do you have for a module?`. These requests will be executed using your authenticated `$ishSession`.
2. As ISHRemote cmdlets understand the API and many of its concepts, it can **explain system concepts** usage questions. This allows you to query `How many statusses are there?`, `Does the system have any privileges?` which could be followed by `These are labels, any way to use these values in an API?`.
2. As ISHRemote cmdlets understand the API and many of its concepts, it can **explain system concepts** usage questions. This allows you to query `How many statusses are there?`, `Does the system have any privileges?` which could be followed by `These are labels, any way to use these values in an API?`. It can explain existing ISHRemote PowerShell scripts.
3. And because the MCP Tools look a lot like ISHRemote cmdlets, you can reuse your LLM's PowerShell knowledge to **draft PowerShell scripts**. You could query for `can you write me a powershell script to create a user?` or `Can you suggest a rewrite using the faster search cmdlet?`. Although the LLM will not always offer working code, but it does seem to make sure you don't have a blank sheet in front of you. You now have something that you can debug and get to a working state as you can feed it your runtime errors and it will improve the generated code iteratively.


### ISHRemoteMcpServer Setup using Visual Studio Code

Below animation give you an overview on what you need to do to set it up.
Below animation give you an overview on what you need to do to set it up. Important is that your Visual Studio Code Terminal runs a different PowerShell process then the one behind the `ISHRemoteMcpServer` which means that any `$ishSession` is not reused which could lead to initial confusion.

![ISHRemote-8.2--ISHRemoteMcpServerSetupDemo 1024x512](./Images/ISHRemote-8.2--ISHRemoteMcpServerSetupDemo.gif)

Expand Down Expand Up @@ -104,6 +104,8 @@ A list of reminders and known issues which we noticed while experimenting...
* OpenAPI proxies of Access Management and Tridion Docs CMS were refreshed. The Access Management API proxies, accessible over `$ishSession.OpenApiAM10Client`, changed and standardized as all API OperationIds are no longer written in CamelCase but Snake_Case. So for example `$ishhSession.OpenApiAM10Client.IdentityProvidersGetAsync()` becomes `$ishhSession.OpenApiAM10Client.IdentityProviders_GetAsync()` with one extra underscore. #223
* `Remove-IshBaselineItem` used version `999999` when removing an entry from the baseline. Since 15.2.0 this has to be the correct version or an empty version, but not the wrong `999999` version. #205 Thanks @OlegTokar
* Rewrite cmdlet `Add-IshBackgroundTask` to not use Microsoft.IdentityModel.Tokens.CollectionUtilities.IsNullOrEmpty extension method which was made `private` (and no longer `public`) in version 8.0.0 of `Microsoft.IdentityModel.Tokens.dll` #216 Thanks @ddemeyer
* Tridion Docs 15.3.0 will be shipped to be running on Windows Server. There is however a parallel track where non-deprecated functionality of 15.3.0 runs in Linux-based containers. Non-deprecated means that the already deprecated features like `AsmxSoapWithAuthenticationContext` and `WcfSoapWithWsTrust` will be removed in next major version 16.0.0. Do note that `WcfSoapWithOpenIdConnect` and `OpenApiWithOpenIdConnect` are predicted to remain working. Refactored the Pester tests to detect this situtation. #230 Thanks @ddemeyer



## Breaking Changes - Cmdlets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ Describe "Get-IshDocumentObj" {
$tempFilePath = (New-TemporaryFile).FullName

# amount of test data to generate
$ishDocumentObjTopicsCount = 1000
$ishDocumentObjMapsCount = 1000
$ishDocumentObjLibsCount = 1000
$ishDocumentObjImagesCount = 1000
$ishDocumentObjOthersCount = 1000
$ishDocumentObjTopicsCount = 10
$ishDocumentObjMapsCount = 10
$ishDocumentObjLibsCount = 10
$ishDocumentObjImagesCount = 10
$ishDocumentObjOthersCount = 10
}
Context "Get-IshDocumentObj Generate Performance Test Data within the test" {
BeforeAll {
Expand Down Expand Up @@ -102,6 +102,7 @@ Describe "Get-IshDocumentObj" {
}
}
}
<#
Context "Get-IshDocumentObj Query 5 times per test" {
BeforeAll {
$ishDocumentObjTopics = Get-IshFolder -IshFolder $global:ishFolderCmdlet -Recurse -FolderTypeFilter ISHModule | Get-IshFolderContent
Expand Down Expand Up @@ -287,14 +288,35 @@ Describe "Get-IshDocumentObj" {
Get-IshDocumentObj -IshObject ($allIShDocumentObjs | Get-Random -Count 5000)
Get-IshDocumentObj -IshObject ($allIShDocumentObjs | Get-Random -Count 5000)
}
}
}
#>
Context "Direct OpenAPI calls 5 times per test" {
BeforeAll {
$ishDocumentObjTopics = Get-IshFolder -IshFolder $global:ishFolderCmdlet -Recurse -FolderTypeFilter ISHModule | Get-IshFolderContent
$ishDocumentObjMaps = Get-IshFolder -IshFolder $global:ishFolderCmdlet -Recurse -FolderTypeFilter ISHMasterDoc | Get-IshFolderContent
$ishDocumentObjLibs = Get-IshFolder -IshFolder $global:ishFolderCmdlet -Recurse -FolderTypeFilter ISHLibrary | Get-IshFolderContent
$ishDocumentObjImages = Get-IshFolder -IshFolder $global:ishFolderCmdlet -Recurse -FolderTypeFilter ISHIllustration | Get-IshFolderContent
$ishDocumentObjOthers = Get-IshFolder -IshFolder $global:ishFolderCmdlet -Recurse -FolderTypeFilter ISHTemplate | Get-IshFolderContent
$allIShDocumentObjs = $ishDocumentObjTopics + $ishDocumentObjMaps + $ishDocumentObjLibs + $ishDocumentObjImages + $ishDocumentObjOthers
}
It "Get-IShDocumentObj by LogicalId for 1 (RetrieveMetadata equals API30.GetDocumentObjectListByLogicalId)" {
$ishSession.DefaultRequestedMetadata="All"
Get-IshDocumentObj -LogicalId $ishDocumentObjTopics[0].IshRef
}
It "Get-IShDocumentObj by IshLngref for 1 (RetrieveMetadataByIshLngRefs equals API30.GetDocumentObjectListByLanguageCardId)" {
$ishSession.DefaultRequestedMetadata="All"
Get-IshDocumentObj -IshObject $ishDocumentObjTopics[1]
}
}
}

AfterAll {
#
Write-Host ("Running "+$cmdletName+" Test Data and Variables cleanup")
$folderCmdletRootPath = (Join-Path $folderTestRootPath $cmdletName)
try { Get-IshFolder -IshSession $ishSession -FolderPath $folderCmdletRootPath -Recurse | Get-IshFolderContent -IshSession $ishSession | Remove-IshDocumentObj -IshSession $ishSession -Force } catch { }
try { Remove-IshFolder -IshSession $ishSession -FolderPath $folderCmdletRootPath -Recurse } catch { }
try { Remove-Item $tempFilePath -Force } catch { }
#>
}

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
using System.Linq;
using System.Management.Automation;
using System.ServiceModel;
using System.Threading;
using Trisoft.ISHRemote.Exceptions;
using Trisoft.ISHRemote.ExtensionMethods;
using Trisoft.ISHRemote.HelperClasses;
using Trisoft.ISHRemote.Objects;
using Trisoft.ISHRemote.Objects.Public;
Expand Down Expand Up @@ -110,9 +112,15 @@ public SwitchParameter IncludeData
[AllowEmptyCollection]
public IshObject[] IshObject { get; set; }

/// <summary>
/// <para type="description">Switch parameter to use REST API (OpenAPI) instead of SOAP API for retrieval. This is experimental in Phase 1.</para>
/// </summary>
[Parameter(Mandatory = false, ParameterSetName = "ParameterGroup")]
public SwitchParameter UseREST { get; set; }






#region Private fields

Expand Down Expand Up @@ -216,24 +224,85 @@ protected override void ProcessRecord()
var statusFilter = EnumConverter.ToStatusFilter<DocumentObj25ServiceReference.StatusFilter>(StatusFilter);
if (!_includeData)
{
//RetrieveMetadata
WriteDebug($"Retrieving LogicalId.length[{LogicalId.Length}] StatusFilter[{statusFilter}] MetadataFilter.length[{metadataFilter.ToXml().Length}] RequestedMetadata.length[{requestedMetadata.ToXml().Length}] 0/{LogicalId.Length}");
// Divides the list of language card ids in different lists that all have maximally MetadataBatchSize elements
List<List<string>> dividedLogicalIdsList = DivideListInBatches<string>(LogicalId.ToList(), IshSession.MetadataBatchSize);
int currentLogicalIdCount = 0;
foreach (List<string> logicalIdBatch in dividedLogicalIdsList)
if (!UseREST)
{
// Process language card ids in batches
string xmlIshObjects = IshSession.DocumentObj25.RetrieveMetadata(
logicalIdBatch.ToArray(),
statusFilter,
metadataFilter.ToXml(),
requestedMetadata.ToXml());
IshObjects retrievedObjects = new IshObjects(ISHType, xmlIshObjects);
returnIshObjects.AddRange(retrievedObjects.Objects);
currentLogicalIdCount += logicalIdBatch.Count;
WriteDebug($"Retrieving LogicalId.length[{logicalIdBatch.Count}] StatusFilter[{statusFilter}] MetadataFilter.length[{metadataFilter.ToXml().Length}] RequestedMetadata.length[{requestedMetadata.ToXml().Length}] {currentLogicalIdCount}/{LogicalId.Length}");
//RetrieveMetadata via SOAP (default implementation)
WriteDebug($"[SOAP] Retrieving LogicalId.length[{LogicalId.Length}] StatusFilter[{statusFilter}] MetadataFilter.length[{metadataFilter.ToXml().Length}] RequestedMetadata.length[{requestedMetadata.ToXml().Length}] 0/{LogicalId.Length}");
// Divides the list of language card ids in different lists that all have maximally MetadataBatchSize elements
List<List<string>> dividedLogicalIdsList = DivideListInBatches<string>(LogicalId.ToList(), IshSession.MetadataBatchSize);
int currentLogicalIdCount = 0;
foreach (List<string> logicalIdBatch in dividedLogicalIdsList)
{
// Process language card ids in batches
string xmlIshObjects = IshSession.DocumentObj25.RetrieveMetadata(
logicalIdBatch.ToArray(),
statusFilter,
metadataFilter.ToXml(),
requestedMetadata.ToXml());
IshObjects retrievedObjects = new IshObjects(ISHType, xmlIshObjects);
returnIshObjects.AddRange(retrievedObjects.Objects);
currentLogicalIdCount += logicalIdBatch.Count;
WriteDebug($"[SOAP] Retrieving LogicalId.length[{logicalIdBatch.Count}] StatusFilter[{statusFilter}] MetadataFilter.length[{metadataFilter.ToXml().Length}] RequestedMetadata.length[{requestedMetadata.ToXml().Length}] {currentLogicalIdCount}/{LogicalId.Length}");
}
}
else
{
//RetrieveMetadata via REST/OpenAPI (experimental Phase 1 implementation)
WriteDebug($"[REST] Retrieving LogicalId.length[{LogicalId.Length}] StatusFilter[{StatusFilter}] MetadataFilter fields[{metadataFilter.Fields().Length}] RequestedMetadata fields[{requestedMetadata.Fields().Length}]");

// Convert ISHRemote types to OpenAPI types
var openApiStatusFilter = StatusFilter.ToOpenApiISH30StatusFilter();
var filterFields = metadataFilter.ToOpenApiISH30FilterFieldValues();
var requestedFields = requestedMetadata.ToOpenApiISH30RequestedFields();

// Divide logical IDs into batches (same batch size as SOAP for consistency)
List<List<string>> dividedLogicalIdsList = DivideListInBatches<string>(LogicalId.ToList(), IshSession.MetadataBatchSize);
int currentLogicalIdCount = 0;

foreach (List<string> logicalIdBatch in dividedLogicalIdsList)
{
// Build the request object for this batch
var getDocumentObjectRequest = new OpenApiISH30.GetDocumentObjectListByLogicalId
{
LogicalIds = logicalIdBatch,
StatusFilter = openApiStatusFilter,
FilterFields = filterFields,
Fields = requestedFields,
SelectedProperties = OpenApiISH30.SelectedProperties.Id,
FieldGroup = requestedMetadata.Fields().Length > 0 ? OpenApiISH30.FieldGroup.None : OpenApiISH30.FieldGroup.Basic,
IncludeLinks = false,
IncludePartialItems = false
};

WriteDebug($"[REST] Calling GetDocumentObjectListByLogicalIdAsync for batch {currentLogicalIdCount}/{LogicalId.Length}");

// Make the async call synchronously (using GetAwaiter().GetResult() for PowerShell compatibility)
var documentObjects = IshSession.OpenApiISH30Client.GetDocumentObjectListByLogicalIdAsync(
getDocumentObjectRequest,
CancellationToken.None)
.GetAwaiter()
.GetResult();

WriteDebug($"[REST] Retrieved {documentObjects.Count} document objects from OpenAPI");

// TODO [Phase 2]: Convert OpenApiISH30.DocumentObject collection to IshObjects
// For Phase 1, we log that REST API was called successfully but cannot yet process results
WriteWarning($"[REST Phase 1] Successfully retrieved {documentObjects.Count} objects via REST API, but conversion to IshObjects not yet implemented. Falling back to SOAP for this batch.");

// Fallback to SOAP for actual data retrieval in Phase 1
string xmlIshObjects = IshSession.DocumentObj25.RetrieveMetadata(
logicalIdBatch.ToArray(),
statusFilter,
metadataFilter.ToXml(),
requestedMetadata.ToXml());
IshObjects retrievedObjects = new IshObjects(ISHType, xmlIshObjects);
returnIshObjects.AddRange(retrievedObjects.Objects);

currentLogicalIdCount += logicalIdBatch.Count;
WriteDebug($"[REST] Processed batch {currentLogicalIdCount}/{LogicalId.Length}");
}
}

}
else
{
Expand Down
Loading
Loading