diff --git a/.vscode/Debug-IShRemoteMCPServer.ps1 b/.vscode/Debug-IShRemoteMCPServer.ps1
index 1f66e4df..199b6781 100644
--- a/.vscode/Debug-IShRemoteMCPServer.ps1
+++ b/.vscode/Debug-IShRemoteMCPServer.ps1
@@ -12,7 +12,7 @@ Start-IshRemoteMcpServer -CmdletsToRegister Get-Help, New-IshSession, Get-IshUse
-ActivateWhileLoop $true `
-LogFilePath "$PSScriptRoot\..\IshRemoteMcpServer.log"
#>
-Start-IshRemoteMcpServer -CmdletsToRegister (((Get-Command -Module ISHRemote -ListImported -CommandType Cmdlet).Name) + "Get-Help") `
+Start-IshRemoteMcpServer -CmdletsToRegister (((Get-Command -Module ISHRemote -ListImported -CommandType Cmdlet).Name)) `
-ActivateWhileLoop $true `
-LogFilePath "$PSScriptRoot\..\IshRemoteMcpServer.log"
#>
diff --git a/.vscode/mcp.json b/.vscode/mcp.json
index 9004e5d3..94752403 100644
--- a/.vscode/mcp.json
+++ b/.vscode/mcp.json
@@ -6,7 +6,7 @@
"args": [
"-NoProfile",
"-Command",
- "& { Start-IshRemoteMcpServer -CmdletsToRegister (((Get-Command -Module ISHRemote -ListImported -CommandType Cmdlet).Name) + \"Get-Help\") -LogFilePath \"D:\\GITHUB\\ISHRemote\\IshRemoteMcpServer.log\" }"
+ "& { Start-IshRemoteMcpServer -CmdletsToRegister ((Get-Command -Module ISHRemote -ListImported -CommandType Cmdlet).Name) -LogFilePath \"D:\\GITHUB\\ISHRemote\\IshRemoteMcpServer.log\" }"
]
},
"IshRemoteMcpServerDEBUG": {
diff --git a/Doc/ReleaseNotes-ISHRemote-8.2.md b/Doc/ReleaseNotes-ISHRemote-8.2.md
index 6627dad7..91cb2895 100644
--- a/Doc/ReleaseNotes-ISHRemote-8.2.md
+++ b/Doc/ReleaseNotes-ISHRemote-8.2.md
@@ -9,16 +9,24 @@ High level release notes are on [Github](https://github.com/rws/ISHRemote/releas
This release inherits the v0.1 to v0.14 up to v8.1 development branch and features. All cmdlets and business logic are fully compatible even around authentication. In short, we expect it all to work still :)
-The one that drastically improved implicit authentication and refresh stability. This stability powered the introduction of the `ISHRemoteMcpServer` experiment - an MCP Server that allow natural language quering of your *Tridion Docs* system. Furthermore, ISHRemote v8.2 is the recommended version to use for Tridion Docs 15.3.0 ( #207).
+The one that is required to run on PowerShell 7.6 LTS hosted by .NET 10. Several big quality of life security improvements regarding implicit authentication and refresh stability. This stability powered the introduction of the `ISHRemoteMcpServer` experiment - an MCP Server that allow natural language quering of your *Tridion Docs* system. Furthermore, ISHRemote v8.2 is the recommended version to use for Tridion Docs 15.3.0 ( #207 #235).
### Remember
-* All C# source code of the ISHRemote library is online at [master](https://github.com/rws/ISHRemote/tree/master/Source/ISHRemote/Trisoft.ISHRemote), including handling of the different [Connection](https://github.com/rws/ISHRemote/tree/master/Source/ISHRemote/Trisoft.ISHRemote/Connection) protocols in a NET 4.8 and NET 6.0+ style.
+* All C# source code of the ISHRemote library is online at [master](https://github.com/rws/ISHRemote/tree/master/Source/ISHRemote/Trisoft.ISHRemote), including handling of the different [Connection](https://github.com/rws/ISHRemote/tree/master/Source/ISHRemote/Trisoft.ISHRemote/Connection) protocols in a NET 4.8, NET 6.0 and .NET 10.0+ style.
* All PowerShell-based Pester integration tests are located per cmdlet complying with the `*.tests.ps1` file naming convention. See for example [AddIshDocumentObj.Tests.ps1](https://github.com/rws/ISHRemote/tree/master/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/DocumentObj/AddIshDocumentObj.Tests.ps1) or [TestIshValidXml.Tests.ps1](https://github.com/rws/ISHRemote/tree/master/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/FileProcessor/TestIshValidXml.Tests.ps1)
The below text describes the delta compared to fielded release ISHRemote v8.1.
+## Platform Support for PowerShell 7.6
+
+PowerShell 7.6, a Long Term Service (LTS) release, hosted on .NET 10 (LTS) required code changes because of breaking changes between .NET 8 and .NET 10. When using ISHRemote v8.1 or earlier, the most prominent error on a `New-IshSession` invoke is `Could not load type 'System.IdentityModel.Tokens.SecurityKeyType' from assembly 'System.ServiceModel.Security` which should trigger you to upgrade to this release. Along the way refactored some warnings `X509Certificate2` and `ServicePointManager` usage. The chosen solution is to have 3 runtimes:
+* Kept Windows-only .NET Framework 4.8 for usage in Windows PowerShell 5.1 with as little code changes as feasible.
+* Kept cross-platform .NET 6.0 for usage in cross-platform PowerShell 7.2 and 7.4 with as little code changes as feasible. Do note that these platforms are out-of-support by Microsoft; in turn ISHRemote considers these deprecated as well.
+* Introduced cross-platform .NET 10.0 for usage in cross-platform PowerShell 7.6. This variation received all possible third-party library updates as well; ranging from Duende.* over System.ServiceModel.* to supporting library like System.Text.Json and more. #235
+
+
## Stability improved by actively recovering interactive sessions
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.`.
@@ -28,11 +36,11 @@ In this ISHRemote version, the session will attempt to get a new token automatic
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`.
-## Experimental MCP Server for Tridion Docs CMS powered by ISHRemote
+## MCP Server for Tridion Docs CMS powered by ISHRemote
### ISHRemoteMcpServer Introduction
-Tridion Docs CMS has a very rich API which opens up all the power of Organize Space, Draft Space, Publication Manager, and more. ISHRemote is a PowerShell library that abstracts some authentication and metadata modelling complexity from the Tridion Docs API in an opionated way like default metadata. ISHRemote also comes with a built-in help for every cmdlet - or should I say MCP Tool - as you can see in for example `Get-Help New-IshSession -Detailed`. It lists purpose, parameters, syntax and examples - this for every cmdlet.
+Tridion Docs CMS has a very rich API which opens up all the power of Organize Space, Draft Space, Publication Manager, and more. ISHRemote is a PowerShell library that abstracts some authentication and metadata modelling complexity from the Tridion Docs API in an opionated way like default metadata. ISHRemote also comes with a built-in help for every cmdlet - or should I say MCP Tool - as you can see in for example `Get-Help New-IshSession -Detailed`. It lists purpose, parameters, syntax and examples - this for every cmdlet. #213 #233
Model Concept Protocol ([MCP](https://modelcontextprotocol.io/docs/getting-started/intro)) is the language to offer tools to your chosen Large Language Model ([LLM](https://en.wikipedia.org/wiki/Large_language_model)) to have smart data interactions. Where in the past the LLMs had access to functions to for example find out the weather in some location, now you can offer it a tool box to access a specific domain like *Tridion Docs*. MCP [Tools](https://modelcontextprotocol.io/docs/learn/server-concepts) enable AI models to perform actions. Each tool defines a specific operation with typed inputs and outputs. The model requests tool execution based on context.
@@ -50,10 +58,10 @@ Below animation give you an overview on what you need to do to set it up. Import
Below the steps in some more detail...
1. You need ISHRemote v8.2+ (PreRelease) installed in your PowerShell v7 (not Windows PowerShell!). See [Installation-ISHRemote-8.0.md](./Installation-ISHRemote-8.0.md) for guidance. Make sure that it works by calling the classic `New-IshSession -WsBaseUrl https://ish.example.com/ISHWS/`
-2. Open your installed [Visual Studio Code](https://code.visualstudio.com) preferably to a working folder where you have or plan to have your scripts saved. Now you need to create or extend your `.vscode/mcp.json` with the below code block. Optionally use a different log file path, and use the double backslashes to comply with the json file syntax.
+2. Open your installed [Visual Studio Code](https://code.visualstudio.com) preferably to a working folder where you have or plan to have your scripts saved. Now you need to create or extend your `.vscode/mcp.json` with the below code block. Optionally use a different log file path, and use the double backslashes to comply with the json file syntax. Or you can run without client-side logging by removing ` -LogFilePath \"$env:TEMP\\IshRemoteMcpServer.log\"`.
3. Start the `IshRemoteMcpServer` using the decorator `Start`
4. Go to your CoPilot, put it in `Agent` mode. After typing a hash (`#`), you should see the registered MCP Tools which look a lot like cmdlet name pop-up, starting with `#Add-IshAnnotation`.
-5. Now set your LLM to `Claude Sonnet 4.5` (or similar). In the chat enter `Create a new ishsession to https://ish.example.com/ISHWS/`, where the url is an existing url.
+5. Now set your LLM to `Claude Sonnet 4.5` (or better). In the chat enter `Create a new ishsession to https://ish.example.com/ISHWS/`, where the url is an existing url.
```json
{
@@ -120,17 +128,10 @@ All cmdlets and business logic are fully compatible.
## Breaking Changes - Platform
-* Replaced package references of deprecated `IdentityModel.OidcClient` to supported `Duende.IdentityModel.OidcClient` plus matching code changes. More details in the releases notes and on #220
-
-| PackageReference From Version | PackageReference To Version | Remarks |
-| ----------------------------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| IdentityModel 7.0.0 | Duende.IdentityModel 7.1.0 | |
-| IdentityModel.OidcClient 7.0.0 | Duende.IdentityModel.OidcClient 6.0.1 | |
-| IdentityModel.OidcClient.IdentityTokenValidator 6.0.0 | \- | With the removal of Hybrid Flow support from (Duende.)IdentityModel.OidcClient, it is not necessary anymore to validate id_tokens. However, You can still do you own validation via our extensibility points if desired. See [https://community.auth0.com/t/managing-tokens-in-net-maui/101577/25?page=2](https://community.auth0.com/t/managing-tokens-in-net-maui/101577/25?page=2) |
-| Microsoft.PowerShell.Commands.Management 7.2.23 | Microsoft.PowerShell.Commands.Management 7.2.24 | Unsupported PowerShell 7.2/NET6 but all works well; will wait for future 7.6/NET10 or higher to bump this package and the underlying net6.0/Trisoft.ISHRemote.dll compilation. |
-| System.Runtime.CompilerServices.Unsafe 6.0.0 | System.Runtime.CompilerServices.Unsafe 6.1.2 | |
-| System.Numerics.Vectors 4.5.0 | System.Numerics.Vectors 4.5.0 | Tried 4.6.1 \`ApplicationException: GetTokensOverClientCredentialsAsync Access Error[Could not load file or assembly 'System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.]; either invalid ClientId/ClientSecret combination or expired ClientSecret.\`Assembly is lock stepped with System.ServiceModel packages, so rolled back to earlier 4.5.0 |
-| System.Text.Json 8.0.5 | System.Text.Json 8.0.6 | |
+* Replaced package references of deprecated `IdentityModel.OidcClient` to supported `Duende.IdentityModel.OidcClient` plus matching code changes. More details in on #220
+ * With the removal of Hybrid Flow support from (Duende.)IdentityModel.OidcClient, it is not necessary anymore to validate id_tokens. However, You can still do you own validation via our extensibility points if desired. See https://community.auth0.com/t/managing-tokens-in-net-maui/101577/25?page=2
+* Tried 4.6.1 `ApplicationException: GetTokensOverClientCredentialsAsync Access Error[Could not load file or assembly 'System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.]; either invalid ClientId/ClientSecret combination or expired ClientSecret.`Assembly is lock stepped with System.ServiceModel packages, so rolled back to earlier 4.5.0
+* Added .NET 10 runtime next to .NET 4.8 and .NET 6. All third-party libraries are updated for .NET 10 only. More details in on #235
## Known Issues
@@ -168,3 +169,5 @@ Below is not an official performance compare, but a recurring thing noticed alon
| ISHRemote 8.2.13001.0 | PowerShell 7.5.3 on .NET 9.0.8 | LEUDEVDDE...@15.3.0b2216 | Tests completed in 111.41s AND Tests Passed: 1071, Failed: 0, Skipped: 4, Inconclusive: 0, NotRun: 0 |
| ISHRemote 8.2.13106.0 | Windows PowerShell 5.1 on .NET 4.8.1 | LEUDEVDDE...@15.3.0b2303 | Tests completed in 151.73s AND Tests Passed: 1071, Failed: 0, Skipped: 4, Inconclusive: 0, NotRun: 0 |
| ISHRemote 8.2.13106.0 | PowerShell 7.5.4 on .NET 9.0.10 | LEUDEVDDE...@15.3.0b2303 | Tests completed in 144.6s AND Tests Passed: 1071, Failed: 0, Skipped: 4, Inconclusive: 0, NotRun: 0 |
+| ISHRemote 8.2.13523.0 | PowerShell 7.5.4 on .NET 9.0.10 | LEUDEVDDE...@15.3.0b2303 | Tests completed in 141.61s AND Tests Passed: 1128, Failed: 0, Skipped: 4, Inconclusive: 0, NotRun: 0 |
+| ISHRemote 8.2.13523.0 | PowerShell 7.6.0 on .NET 10.0.5 | LEUDEVDDE...@15.3.0b2303 | Tests completed in s AND Tests Passed: 1128, Failed: 0, Skipped: 4, Inconclusive: 0, NotRun: 0 |
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapBearerCredentials.cs b/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapBearerCredentials.cs
index b03f9b04..9d44c1d6 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapBearerCredentials.cs
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapBearerCredentials.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright (c) 2014 All Rights Reserved by the SDL Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -89,7 +89,11 @@ private static X509Certificate2 CreateX509Certificate2(string certName = "Client
var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(1));
var password = Guid.NewGuid().ToString();
+#if NET10_0_OR_GREATER
+ return X509CertificateLoader.LoadPkcs12(cert.Export(X509ContentType.Pfx, password), password);
+#else
return new X509Certificate2(cert.Export(X509ContentType.Pfx, password), password);
+#endif
}
private static GenericXmlSecurityToken WrapJwt(string jwt)
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithWsTrustConnection.cs b/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithWsTrustConnection.cs
index fff726e0..d2aa2ec2 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithWsTrustConnection.cs
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithWsTrustConnection.cs
@@ -294,7 +294,7 @@ public InfoShareWcfSoapWithWsTrustConnection(ILogger logger, HttpClient httpClie
EndpointAddress issuerAddress = new EndpointAddress(IssuerWSTrustEndpointUri);
WSTrustTokenParameters tokenParameters = WSTrustTokenParameters.CreateWS2007FederationTokenParameters(issuerBinding, issuerAddress); // WS-Trust 1.3 is 2007
- tokenParameters.KeyType = SecurityKeyType.SymmetricKey;
+ tokenParameters.KeyType = SecurityKeyType.SymmetricKey; //"http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey";
// CacheIssuedTokens, MaxIssuedCachingTime, and IssuedTokenRenewalThresholdPercentage These properties indicate whether tokens should be
// cached and for how long.In many cases, these properties don�t need to be set as the defaults(tokens are cached for 60 % of their lifetime) are sufficient.
tokenParameters.CacheIssuedTokens = true;
@@ -1447,8 +1447,7 @@ private GenericXmlSecurityToken IssueToken()
issuerWS2007HttpBinding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Default;
var tokenParams = WSTrustTokenParameters.CreateWS2007FederationTokenParameters(issuerWS2007HttpBinding, new EndpointAddress(IssuerWSTrustEndpointUri));
- tokenParams.KeyType = SecurityKeyType.BearerKey; // "http://schemas.microsoft.com/idfx/keytype/bearer";
-
+ tokenParams.KeyType = SecurityKeyType.BearerKey; //"http://schemas.microsoft.com/idfx/keytype/bearer";
var clientCredentials = new ClientCredentials();
clientCredentials.UserName.UserName = _connectionParameters.Credential.UserName;
clientCredentials.UserName.Password = _connectionParameters.Credential.Password;
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/HelperClasses/CertificateValidationHelper.cs b/Source/ISHRemote/Trisoft.ISHRemote/HelperClasses/CertificateValidationHelper.cs
index b5216a8f..e8fdf3b8 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/HelperClasses/CertificateValidationHelper.cs
+++ b/Source/ISHRemote/Trisoft.ISHRemote/HelperClasses/CertificateValidationHelper.cs
@@ -33,7 +33,9 @@ namespace Trisoft.ISHRemote.HelperClasses
///
public static class CertificateValidationHelper
{
- private static RemoteCertificateValidationCallback _orignalCallback;
+#if NET48
+ private static RemoteCertificateValidationCallback _originalCallback;
+#endif
private static readonly ILogger _logger = TrisoftCmdletLogger.Instance();
private static bool OnValidateCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
@@ -63,13 +65,14 @@ private static bool OnValidateCertificate(object sender, X509Certificate certifi
}
}
+#if NET48
///
/// Sets our custom AppDomain ssl/certificate overwrite callback using ServicePointManager, including a backup of any existing callback
///
public static void OverrideCertificateValidation()
{
_logger.WriteWarning("Applying certificate validation overwrite for the AppDomain. (OnValidateCertificate)");
- _orignalCallback = ServicePointManager.ServerCertificateValidationCallback;
+ _originalCallback = ServicePointManager.ServerCertificateValidationCallback;
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(OnValidateCertificate);
ServicePointManager.Expect100Continue = true;
}
@@ -80,7 +83,8 @@ public static void OverrideCertificateValidation()
public static void RestoreCertificateValidation()
{
_logger.WriteDebug("Restoring backup of the earlier saved certificate validation for the AppDomain. (OnValidateCertificate)");
- ServicePointManager.ServerCertificateValidationCallback = _orignalCallback;
+ ServicePointManager.ServerCertificateValidationCallback = _originalCallback;
}
+#endif
}
}
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.psm1 b/Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.psm1
index 5902862a..c21ab3d5 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.psm1
+++ b/Source/ISHRemote/Trisoft.ISHRemote/ISHRemote.psm1
@@ -1,4 +1,4 @@
-# PowerShell Module file in the same folder as the AssemblyName.DLL with the name AssemblyName.PSM1
+# PowerShell Module file in the same folder as the AssemblyName.DLL with the name AssemblyName.PSM1
# This file will add aliasses, including backward compatible entries
# SRC
# http://stackoverflow.com/questions/13583604/is-there-a-way-to-add-alias-to-powershell-cmdlet-programmatically
@@ -26,29 +26,33 @@
#
# Required for Expand-ISHParameter.ps1 because $global:options might not exists
-Set-StrictMode -Off
+Set-StrictMode -Off
# Set up some helper variables to make it easier to work with the module
-$PSModule = $ExecutionContext.SessionState.Module
-$PSModuleRoot = $PSModule.ModuleBase
+$PSModule = $ExecutionContext.SessionState.Module
+$PSModuleRoot = $PSModule.ModuleBase
# Import the appropriate nested binary module based on the current PowerShell version
-$binaryModuleRoot = $PSModuleRoot
+$binaryModuleRoot = $PSModuleRoot
-if (($PSVersionTable.Keys -contains "PSEdition") -and ($PSVersionTable.PSEdition -eq 'Desktop')) {
- $binaryModuleRoot = Join-Path -Path $PSModuleRoot -ChildPath 'net48'
+if (($PSVersionTable.Keys -contains "PSEdition") -and ($PSVersionTable.PSEdition -eq 'Desktop')) {
+ $binaryModuleRoot = Join-Path -Path $PSModuleRoot -ChildPath 'net48'
}
else
{
- if ($PSVersionTable.PSVersion -gt [Version]'7.1')
+ if (($PSVersionTable.PSVersion -gt [Version]'7.1') -and ($PSVersionTable.PSVersion -lt [Version]'7.6'))
{
- $binaryModuleRoot = Join-Path -Path $PSModuleRoot -ChildPath 'net6.0'
- }
+ $binaryModuleRoot = Join-Path -Path $PSModuleRoot -ChildPath 'net6.0'
+ }
+ else
+ {
+ $binaryModuleRoot = Join-Path -Path $PSModuleRoot -ChildPath 'net10.0'
+ }
}
Write-Debug ("[" + $MyInvocation.MyCommand + "] Loading [" + $binaryModuleRoot + "] on PSEdition[" + $PSVersionTable.PSEdition + "] + PSVersion[" + $PSVersionTable.PSVersion + "]")
-$binaryModulePath = Join-Path -Path $binaryModuleRoot -ChildPath 'Trisoft.ISHRemote.dll'
-$binaryModule = Import-Module -Name $binaryModulePath -PassThru
+$binaryModulePath = Join-Path -Path $binaryModuleRoot -ChildPath 'Trisoft.ISHRemote.dll'
+$binaryModule = Import-Module -Name $binaryModulePath -PassThru
$privateCmdlet = @(Get-ChildItem -Path $PSScriptRoot\Scripts\Private\*.ps1 -ErrorAction SilentlyContinue -Exclude *.Tests.ps1)
$publicCmdlet = @(Get-ChildItem -Path $PSScriptRoot\Scripts\Public\*.ps1 -ErrorAction SilentlyContinue -Exclude *.Tests.ps1)
@@ -65,9 +69,9 @@ Foreach($import in @($privateCmdlet + $publicCmdlet))
}
}
-Set-StrictMode -Version Latest
+Set-StrictMode -Version Latest
# When the module is unloaded, remove the nested binary module that was loaded with it
-$PSModule.OnRemove = {
- Remove-Module -ModuleInfo $binaryModule
-}
\ No newline at end of file
+$PSModule.OnRemove = {
+ Remove-Module -ModuleInfo $binaryModule
+}
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Objects/Public/IshSession.cs b/Source/ISHRemote/Trisoft.ISHRemote/Objects/Public/IshSession.cs
index b58f3cd0..42c9fbb3 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/Objects/Public/IshSession.cs
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Objects/Public/IshSession.cs
@@ -126,6 +126,7 @@ public IshSession(ILogger logger, string webServicesBaseUrl, string ishUserName,
{
CertificateValidationHelper.OverrideCertificateValidation();
}
+ ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
#endif
_logger.WriteDebug($"Enabling Tls, Tls11, Tls12 and Tls13 security protocols on HttpClientHandler. Timeout[{_timeout}] IgnoreSslPolicyErrors[{_ignoreSslPolicyErrors}]");
if (_ignoreSslPolicyErrors)
@@ -136,7 +137,6 @@ public IshSession(ILogger logger, string webServicesBaseUrl, string ishUserName,
// overwrite certificate handling for HttpClient requests
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
- ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
handler.SslProtocols = (System.Security.Authentication.SslProtocols)(SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13);
_httpClient = new HttpClient(handler)
{
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Invoke-IshRemoteMcpHandleRequest.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Invoke-IshRemoteMcpHandleRequest.Tests.ps1
new file mode 100644
index 00000000..b0d534cc
--- /dev/null
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Invoke-IshRemoteMcpHandleRequest.Tests.ps1
@@ -0,0 +1,202 @@
+BeforeAll {
+ $cmdletName = "Invoke-IshRemoteMcpHandleRequest"
+ Write-Host ("`r`nLoading ISHRemote.PesterSetup.ps1 on PSVersion[" + $psversionTable.PSVersion + "] over BeforeAll-block for MyCommand[" + $cmdletName + "]...")
+ . (Join-Path (Split-Path -Parent $PSCommandPath) "\..\..\ISHRemote.PesterSetup.ps1")
+
+ function Write-IshRemoteLog {
+ param(
+ [Parameter(Mandatory = $true)]
+ [object]$LogEntry
+ )
+ }
+
+}
+
+Describe "Invoke-IshRemoteMcpHandleRequest" {
+ Context "Method Initialize" {
+ BeforeAll {
+ Mock -ModuleName ISHRemote Write-IshRemoteLog { }
+ $request = [PSCustomObject]@{
+ id = 1
+ method = "initialize"
+ params = @{}
+ }
+ $toolsJson = '[]'
+ $resourcesJson = '[]'
+ $instructionsJson = '"test instructions"'
+ $result = Invoke-IshRemoteMcpHandleRequest -Request $request -ToolsListJson $toolsJson -ResourcesListJson $resourcesJson -InstructionsJson $instructionsJson
+ }
+ It "Should return valid JSON" {
+ { $result | ConvertFrom-Json } | Should -Not -Throw
+ }
+ It "Should return jsonrpc 2.0" {
+ $json = $result | ConvertFrom-Json
+ $json.jsonrpc | Should -Be "2.0"
+ }
+ It "Should return matching request id" {
+ $json = $result | ConvertFrom-Json
+ $json.id | Should -Be 1
+ }
+ It "Should contain protocolVersion" {
+ $json = $result | ConvertFrom-Json
+ $json.result.protocolVersion | Should -Be "2024-11-05"
+ }
+ It "Should contain capabilities" {
+ $json = $result | ConvertFrom-Json
+ $json.result.capabilities | Should -Not -BeNullOrEmpty
+ }
+ It "Should contain instructions" {
+ $json = $result | ConvertFrom-Json
+ $json.result.instructions | Should -Be "test instructions"
+ }
+ It "Should contain serverInfo" {
+ $json = $result | ConvertFrom-Json
+ $json.result.serverInfo.name | Should -Not -BeNullOrEmpty
+ $json.result.serverInfo.version | Should -Not -BeNullOrEmpty
+ }
+ }
+
+ Context "Method Ping" {
+ BeforeAll {
+ Mock -ModuleName ISHRemote Write-IshRemoteLog { }
+ $request = [PSCustomObject]@{
+ id = 2
+ method = "ping"
+ params = @{}
+ }
+ $result = Invoke-IshRemoteMcpHandleRequest -Request $request -ToolsListJson '[]' -ResourcesListJson '[]' -InstructionsJson '""'
+ }
+ It "Should return valid JSON" {
+ { $result | ConvertFrom-Json } | Should -Not -Throw
+ }
+ It "Should return jsonrpc 2.0" {
+ $json = $result | ConvertFrom-Json
+ $json.jsonrpc | Should -Be "2.0"
+ }
+ It "Should return matching request id" {
+ $json = $result | ConvertFrom-Json
+ $json.id | Should -Be 2
+ }
+ It "Should return empty result" {
+ $json = $result | ConvertFrom-Json
+ $json.result | Should -BeNullOrEmpty
+ }
+ }
+
+ Context "Method Tools/List" {
+ BeforeAll {
+ Mock -ModuleName ISHRemote Write-IshRemoteLog { }
+ $request = [PSCustomObject]@{
+ id = 3
+ method = "tools/list"
+ params = @{}
+ }
+ $toolsJson = '[{"name":"Get-IshFolder","description":"Test tool"}]'
+ $result = Invoke-IshRemoteMcpHandleRequest -Request $request -ToolsListJson $toolsJson -ResourcesListJson '[]' -InstructionsJson '""'
+ }
+ It "Should return valid JSON" {
+ { $result | ConvertFrom-Json } | Should -Not -Throw
+ }
+ It "Should return jsonrpc 2.0" {
+ $json = $result | ConvertFrom-Json
+ $json.jsonrpc | Should -Be "2.0"
+ }
+ It "Should return matching request id" {
+ $json = $result | ConvertFrom-Json
+ $json.id | Should -Be 3
+ }
+ It "Should contain tools list" {
+ $json = $result | ConvertFrom-Json
+ $json.result.tools | Should -Not -BeNullOrEmpty
+ }
+ }
+
+ Context "Method Tools/Call" {
+ BeforeAll {
+ Mock -ModuleName ISHRemote Write-IshRemoteLog { }
+ $request = [PSCustomObject]@{
+ id = 4
+ method = "tools/call"
+ params = @{
+ name = "Get-Date"
+ arguments = @{
+ Format = "yyyy-MM-dd"
+ }
+ }
+ }
+ $result = Invoke-IshRemoteMcpHandleRequest -Request $request -ToolsListJson '[]' -ResourcesListJson '[]' -InstructionsJson '""'
+ }
+ It "Should return valid JSON" {
+ { $result | ConvertFrom-Json } | Should -Not -Throw
+ }
+ It "Should return jsonrpc 2.0" {
+ $json = $result | ConvertFrom-Json
+ $json.jsonrpc | Should -Be "2.0"
+ }
+ It "Should return matching request id" {
+ $json = $result | ConvertFrom-Json
+ $json.id | Should -Be 4
+ }
+ It "Should contain result content" {
+ $json = $result | ConvertFrom-Json
+ $json.result.content | Should -Not -BeNullOrEmpty
+ }
+ It "Should have content type text" {
+ $json = $result | ConvertFrom-Json
+ $json.result.content[0].type | Should -Be "text"
+ }
+ It "Should have isError false" {
+ $json = $result | ConvertFrom-Json
+ $json.result.isError | Should -Be $false
+ }
+ }
+
+ Context "Method Tools/Call with Error" {
+ BeforeAll {
+ Mock -ModuleName ISHRemote Write-IshRemoteLog { }
+ $request = [PSCustomObject]@{
+ id = 5
+ method = "tools/call"
+ params = @{
+ name = "Non-ExistentCommand"
+ arguments = @{}
+ }
+ }
+ $result = Invoke-IshRemoteMcpHandleRequest -Request $request -ToolsListJson '[]' -ResourcesListJson '[]' -InstructionsJson '""'
+ }
+ It "Should return valid JSON" {
+ { $result | ConvertFrom-Json } | Should -Not -Throw
+ }
+ }
+
+ Context "Unknown Method" {
+ BeforeAll {
+ Mock -ModuleName ISHRemote Write-IshRemoteLog { }
+ $request = [PSCustomObject]@{
+ id = 6
+ method = "unknown/method"
+ params = @{}
+ }
+ $result = Invoke-IshRemoteMcpHandleRequest -Request $request -ToolsListJson '[]' -ResourcesListJson '[]' -InstructionsJson '""'
+ }
+ It "Should return valid JSON" {
+ { $result | ConvertFrom-Json } | Should -Not -Throw
+ }
+ It "Should return error response" {
+ $json = $result | ConvertFrom-Json
+ $json.error | Should -Not -BeNullOrEmpty
+ }
+ It "Should return error code -32601" {
+ $json = $result | ConvertFrom-Json
+ $json.error.code | Should -Be -32601
+ }
+ It "Should return 'Method not found' message" {
+ $json = $result | ConvertFrom-Json
+ $json.error.message | Should -Be "Method not found"
+ }
+ }
+}
+
+AfterAll {
+ Write-Host ("Running "+$cmdletName+" Test Data and Variables cleanup")
+}
\ No newline at end of file
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Invoke-IshRemoteMcpHandleRequest.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Invoke-IshRemoteMcpHandleRequest.ps1
index f36edcdf..1bab7f34 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Invoke-IshRemoteMcpHandleRequest.ps1
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Invoke-IshRemoteMcpHandleRequest.ps1
@@ -1,3 +1,8 @@
+<#
+.DESCRIPTION
+ Processes incoming MCP requests including initialize, ping, tools/list, and tools/call methods.
+ Returns formatted JSON-RPC 2.0 responses for each request type.
+#>
function Invoke-IshRemoteMcpHandleRequest {
param(
[object]$Request,
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpInstructions.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpInstructions.Tests.ps1
new file mode 100644
index 00000000..46568326
--- /dev/null
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpInstructions.Tests.ps1
@@ -0,0 +1,79 @@
+BeforeAll {
+ $cmdletName = "Register-IshRemoteMcpInstructions"
+ Write-Host ("`r`nLoading ISHRemote.PesterSetup.ps1 on PSVersion[" + $psversionTable.PSVersion + "] over BeforeAll-block for MyCommand[" + $cmdletName + "]...")
+ . (Join-Path (Split-Path -Parent $PSCommandPath) "\..\..\ISHRemote.PesterSetup.ps1")
+}
+
+Describe "Register-IshRemoteMcpInstructions" {
+
+ Context "Function Output" {
+ It "Should return valid JSON" {
+ $result = Register-IshRemoteMcpInstructions
+ { $result | ConvertFrom-Json } | Should -Not -Throw
+ }
+
+ It "Should return non-empty string" {
+ $result = Register-IshRemoteMcpInstructions
+ $result | Should -Not -BeNullOrEmpty
+ }
+ }
+
+ Context "Instruction Content" {
+ BeforeAll {
+ $instructions = (Register-IshRemoteMcpInstructions | ConvertFrom-Json)
+ }
+
+ It "Should contain New-IshSession cmdlet reference" {
+ $instructions | Should -Match "New-IShSession"
+ }
+
+ It "Should contain Get-IshTypeFieldDefinition reference" {
+ $instructions | Should -Match "Get-IshTypeFieldDefinition"
+ }
+
+ It "Should contain FilterOperator reference" {
+ $instructions | Should -Match "FilterOperator"
+ }
+
+ It "Should contain field types (String, Number, DateTime, LongText)" {
+ $instructions | Should -Match "String"
+ $instructions | Should -Match "Number"
+ $instructions | Should -Match "DateTime"
+ $instructions | Should -Match "LongText"
+ }
+
+ It "Should contain ISHType object references" {
+ $instructions | Should -Match "IShUser"
+ $instructions | Should -Match "IShFolder"
+ $instructions | Should -Match "IShDocumentObj"
+ }
+
+ It "Should contain level references (logical, version, lng)" {
+ $instructions | Should -Match "logical"
+ $instructions | Should -Match "version"
+ $instructions | Should -Match "lng"
+ }
+
+ It "Should mention PSNoteType properties" {
+ $instructions | Should -Match "PSNoteType"
+ }
+
+ It "Should contain Get-Help cmdlet reference" {
+ $instructions | Should -Match "Get-Help"
+ }
+
+ It "Should mention case-sensitivity" {
+ $instructions | Should -Match "case-sensitive"
+ }
+
+ It "Should contain wildcard operator guidance" {
+ $instructions | Should -Match "percentage"
+ $instructions | Should -Match "%"
+ }
+ }
+}
+
+
+AfterAll {
+ Write-Host ("Running "+$cmdletName+" Test Data and Variables cleanup")
+}
\ No newline at end of file
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpInstructions.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpInstructions.ps1
index c9c55016..a05e8ce2 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpInstructions.ps1
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpInstructions.ps1
@@ -1,3 +1,9 @@
+<#
+.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.
+#>
function Register-IshRemoteMcpInstructions {
$instructions=
@"
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpTool.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpTool.Tests.ps1
index 73bd024f..56e66ea3 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpTool.Tests.ps1
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpTool.Tests.ps1
@@ -13,16 +13,58 @@ BeforeAll {
}
Describe "Register-IshRemoteMcpTool" -Tags "Read" {
- Context "Register-IshRemoteMcpTool" {
+ Context "Register-IshRemoteMcpTool (beware Get-Help is empty if Get-InstalledPSResource still shows ISHRemote installed)" {
BeforeEach{
Mock -ModuleName ISHRemote Write-IshRemoteLog { }
}
- It "Get-IshFolder" {
- $resultJson = Register-IshRemoteMcpTool -FunctionName Get-IshFolder | ConvertFrom-Json
- $expectedJson = '{"name":"Get-IshFolder","description":"Get-IshFolder -FolderId [-IshSession ] [-RequestedMetadata ] [-Recurse] [-Depth ] [-FolderTypeFilter ] []\r\n\r\nGet-IshFolder -FolderPath [-IshSession ] [-RequestedMetadata ] [-Recurse] [-Depth ] [-FolderTypeFilter ] []\r\n\r\nGet-IshFolder -IshFolder [-IshSession ] [-RequestedMetadata ] [-Recurse] [-Depth ] [-FolderTypeFilter ] []\r\n\r\nGet-IshFolder -BaseFolder [-IshSession ] [-RequestedMetadata ] [-Recurse] [-Depth ] [-FolderTypeFilter ] []\n\nThis PowerShell cmdlet has the following parameter sets to choose form where square brackets indicate optional parameters while the other parameters are mandatory:\nsyntaxItem\r\n----------\r\n{@{name=Get-IshFolder; CommonParameters=True; parameter=System.Object[]}, @{name=Get-IshFolder; CommonParameters=True; parameter=System.Object[]}, @{n…\n\nThe PowerShell cmdlet has the following examples as inspiration:\n","annotations":{"type":"object","destructiveHint":"false","idempotentHint":"true","readOnlyHint":"true"},"inputSchema":{"type":"object","properties":{"BaseFolder":{"type":"string","description":"No description available for this parameter."},"Depth":{"type":"number","description":"No description available for this parameter."},"FolderId":{"type":"number","description":"No description available for this parameter."},"FolderPath":{"type":"string","description":"No description available for this parameter."},"FolderTypeFilter":{"type":"string","description":"No description available for this parameter."},"IshFolder":{"type":"string","description":"No description available for this parameter."},"IshSession":{"type":"string","description":"No description available for this parameter."},"Recurse":{"type":"boolean","description":"No description available for this parameter."},"RequestedMetadata":{"type":"string","description":"No description available for this parameter."}},"required":[]},"returns":{"type":"string","description":"Get-IshFolder"}}' | ConvertFrom-Json
+ It "Full Load of Get-IshFolder cmdlet" {
+ $resultJson = Register-IshRemoteMcpTool -FunctionNameFullLoad @('Get-IshFolder') -FunctionNamePartialLoad @() | ConvertFrom-Json
+ $expectedJson =
+@"
+{"name":"Get-IshFolder","description":"The Get-IshFolder cmdlet retrieves metadata for the folders by providing one of the following input data: - FolderPath string with the separated full folder path - FolderIds array containing identifiers of the folders - BaseFolder enum value referencing the specified root folder - IshFolder[] array passed through the pipeline Query and Reference folders are not supported.\n\nThis PowerShell cmdlet has the following parameter sets to choose form where square brackets indicate optional parameters while the other parameters are mandatory:\nGet-IshFolder -FolderId [-Depth ] [-FolderTypeFilter {ISHNone | ISHModule | ISHMasterDoc | ISHLibrary | ISHTemplate | ISHIllustration | ISHPublication | ISHReference | ISHQuery}] [-IshSession ] [-Recurse ] [-RequestedMetadata ] []\r\n\r\nGet-IshFolder -FolderPath [-Depth ] [-FolderTypeFilter {ISHNone | ISHModule | ISHMasterDoc | ISHLibrary | ISHTemplate | ISHIllustration | ISHPublication | ISHReference | ISHQuery}] [-IshSession ] [-Recurse ] [-RequestedMetadata ] []\r\n\r\nGet-IshFolder -IshFolder [-Depth ] [-FolderTypeFilter {ISHNone | ISHModule | ISHMasterDoc | ISHLibrary | ISHTemplate | ISHIllustration | ISHPublication | ISHReference | ISHQuery}] [-IshSession ] [-Recurse ] [-RequestedMetadata ] []\r\n\r\nGet-IshFolder -BaseFolder {Data | System | Favorites | EditorTemplate} [-Depth ] [-FolderTypeFilter {ISHNone | ISHModule | ISHMasterDoc | ISHLibrary | ISHTemplate | ISHIllustration | ISHPublication | ISHReference | ISHQuery}] [-IshSession ] [-Recurse ] [-RequestedMetadata ] []\n\nThe PowerShell cmdlet has the following examples as inspiration:\n---------- EXAMPLE 1 ----------\r\n\r\n$ishSession = New-IshSession -WsBaseUrl \"https://example.com/ISHWS/\" -PSCredential Admin\r\nGet-IshFolder -FolderPath \"\\General\\__ISHRemote\\Add-IshPublicationOutput\\Pub\"\r\n\r\nNew-IshSession will submit into SessionState, so it can be reused by this cmdlet. Returns the IshFolder object.\r\n---------- EXAMPLE 2 ----------\r\n\r\n$ishSession = New-IshSession -WsBaseUrl \"https://example.com/ISHWS/\" -PSCredential Admin\r\n(Get-IshFolder -BaseFolder Data).name\r\n\r\nNew-IshSession will submit into SessionState, so it can be reused by this cmdlet. Returns the name of the root data folder, typically called 'General'.\r\n---------- EXAMPLE 3 ----------\r\n\r\n$ishSession = New-IshSession -WsBaseUrl \"https://example.com/ISHWS/\" -PSCredential \"Admin\"\r\n$requestedMetadata = Set-IshMetadataFilterField -Name \"FNAME\" -Level \"None\"\r\n$folderId = 7598 # provide a real folder identifier\r\n$ishFolder = Get-IshFolder -FolderId $folderId -RequestedMetaData $requestedMetadata\r\n$retrievedFolderName = $ishFolder.name\r\n\r\nGet folder name using Id with explicit requested metadata\r\n---------- EXAMPLE 4 ----------\r\n\r\n$ishSession = New-IshSession -WsBaseUrl \"https://example.com/ISHWS/\" -PSCredential \"Admin\"\r\n$ishFolders = Get-IshFolder -FolderPath \"General\\Myfolder\" -FolderTypeFilter @(\"ISHModule\", \"ISHMasterDoc\", \"ISHLibrary\") -Recurse\r\n\r\nGet folders recursively with filtering on folder type\r\n---------- EXAMPLE 5 ----------\r\n\r\nNew-IshSession -WsBaseUrl \"https://example.com/ISHWS/\"\r\n$imageCount = 0\r\n$xmlCount = 0\r\nGet-IshFolder -FolderPath \"General\\Myfolder\" -FolderTypeFilter @(\"ISHIllustration\", \"ISHModule\", \"ISHMasterDoc\", \"ISHLibrary\") -Recurse | \r\nGet-IshFolderContent -VersionFilter \"\" | \r\nForEach-Object -Process { \r\n if ($_.IshType -in @(\"ISHIllustration\")) { ++$imageCount }\r\n if ($_.IshType -in @(\"ISHModule\", \"ISHMasterDoc\", \"ISHLibrary\")) { ++$xmlCount }\r\n}\r\nWrite-Host (\"imageCount[\"+$imageCount+\"]\")\r\nWrite-Host (\"xmlCount[\"+$xmlCount+\"]\")\r\n\r\nVarious statistics can be gathered by crawling across many API calls. This sample recursively goes over some subfolder, and retrieves all content objects in the folder and aggregates to a rough count. The ForEach-Object construct is important as it only keeps the essence, the counters, and avoids keeping all objects retrieved over the API in memory - potentially running out of client-side/PowerShell memory.\r\n---------- EXAMPLE 6 ----------\r\n\r\nNew-IshSession -WsBaseUrl \"https://example.com/ISHWS/\"\r\n$metadataFilter = Set-IshMetadataFilterField -Level Lng -Name FISHPUBSTATUS -ValueType Element -FilterOperator In -Value VPUBSTATUSUNPUBLISHFAILED\r\n$ishObjects = Get-IshFolder -BaseFolder Data -FolderTypeFilter @(\"ISHPublication\") -Recurse | \r\n Get-IshFolderContent -IshFolder $ishFolders -VersionFilter LATEST -LanguagesFilter ('en-US','de-DE') -MetadataFilter $metadataFilter\r\n\r\nThis Get-IshFolder iteratively loops your repository folder structure and only passes Publication folders to the next cmdlet. Then Get-IshFolderContent cmdlet only retrieves LATEST versions of Publications with a languages filter and metadata filter.","annotations":{"type":"object","destructiveHint":"false","idempotentHint":"true","readOnlyHint":"true"},"inputSchema":{"type":"object","properties":{"BaseFolder":{"type":"string","description":"The BaseFolder enumeration to get subfolders for the specified root folder\r\n\r\n\r\nPossible values: Data, System, Favorites, EditorTemplate"},"Depth":{"type":"number","description":"Perform recursive retrieval of up to Depth of the provided incoming folder(s)"},"FolderId":{"type":"number","description":"Unique folder identifier"},"FolderPath":{"type":"string","description":"Separated string with the full folder path, e.g. \"General\\Project\\Topics\""},"FolderTypeFilter":{"type":"string","description":"Recursive retrieval will loop all folder, this filter will only return folder matching the filter to the pipeline\r\n\r\n\r\nPossible values: ISHNone, ISHModule, ISHMasterDoc, ISHLibrary, ISHTemplate, ISHIllustration, ISHPublication, ISHReference, ISHQuery"},"IshFolder":{"type":"string","description":"Folders for which to retrieve the metadata. This array can be passed through the pipeline or explicitly passed via the parameter."},"IshSession":{"type":"string","description":"The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet."},"Recurse":{"type":"boolean","description":"Perform recursive retrieval of the provided incoming folder(s)"},"RequestedMetadata":{"type":"string","description":"The metadata fields to retrieve"}},"required":[]},"returns":{"type":"string","description":"Get-IshFolder"}}
+"@ | ConvertFrom-Json
+ #NAME
$resultJson.name | Should -Be $expectedJson.name
+ #DESCRIPTION or SYNOPSIS
+ $resultJson.description -like 'The Get-IshFolder cmdlet retrieves metadata for the folders by providing one of the following input data*' | Should -Be $true
+ #EXAMPLES
+ $resultJson.description -like '*PowerShell cmdlet has the following examples as inspiration*' | Should -Be $true
+ #SYNTAX or PARAMETER SETS
+ $resultJson.description -like '*This PowerShell cmdlet has the following parameter sets to choose form where square brackets indicate optional parameters while the other parameters are mandatory*' | Should -Be $true
+ #PARAMETERS
+ $resultJson.inputSchema.properties.IShSession.type | Should -Be $expectedJson.inputSchema.properties.IShSession.type
+ $resultJson.inputSchema.properties.IShSession.description | Should -Be $expectedJson.inputSchema.properties.IShSession.description
+ #OUTPUT
$resultJson.returns.type | Should -Be $expectedJson.returns.type
}
+ It "Partial Load of Get-IshFolder cmdlet" {
+ $resultJson = Register-IshRemoteMcpTool -FunctionNameFullLoad @() -FunctionNamePartialLoad @('Get-IshFolder') | ConvertFrom-Json
+ $expectedJson =
+@"
+{"name":"Get-IshFolder","description":"The Get-IshFolder cmdlet retrieves metadata for the folders by providing one of the following input data: - FolderPath string with the separated full folder path - FolderIds array containing identifiers of the folders - BaseFolder enum value referencing the specified root folder - IshFolder[] array passed through the pipeline Query and Reference folders are not supported.","annotations":{"type":"object","destructiveHint":"false","idempotentHint":"true","readOnlyHint":"true"},"inputSchema":{"type":"object","properties":{},"required":[]},"returns":{"type":"string","description":"Get-IshFolder"}}
+"@ | ConvertFrom-Json
+#NAME
+ $resultJson.name | Should -Be $expectedJson.name
+ #DESCRIPTION or SYNOPSIS
+ $resultJson.description -like 'The Get-IshFolder cmdlet retrieves metadata for the folders by providing one of the following input data*' | Should -Be $true
+ #EXAMPLES
+ $resultJson.description -notlike '*PowerShell cmdlet has the following examples as inspiration*' | Should -Be $true
+ #SYNTAX or PARAMETER SETS
+ $resultJson.description -notlike '*This PowerShell cmdlet has the following parameter sets to choose form where square brackets indicate optional parameters while the other parameters are mandatory*' | Should -Be $true
+ #PARAMETERS
+ $resultJson.inputSchema.properties.ToString().Length | Should -Be 0
+ #OUTPUT
+ $resultJson.returns.type | Should -Be $expectedJson.returns.type
+ }
+ It "Full Load of all ISHRemote cmdlets (>100k characters like 288225)" {
+ $resultString = Register-IshRemoteMcpTool -FunctionNameFullLoad (Get-Command -Module ISHRemote -ListImported -CommandType Cmdlet).Name
+ $resultString.Length -gt 100000 | Should -Be $true
+ }
+ It "Partial Load of all ISHRemote cmdlets (<100k characters like 44603)" {
+ $resultString = Register-IshRemoteMcpTool -FunctionNamePartialLoad (Get-Command -Module ISHRemote -ListImported -CommandType Cmdlet).Name
+ $resultString.Length -gt 10000 | Should -Be $true
+ $resultString.Length -lt 100000 | Should -Be $true
+ }
}
}
@@ -30,3 +72,5 @@ AfterAll {
Write-Host ("Running "+$cmdletName+" Test Data and Variables cleanup")
}
+
+
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpTool.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpTool.ps1
index 6877a22c..5320c2ec 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpTool.ps1
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Register-IshRemoteMcpTool.ps1
@@ -1,16 +1,45 @@
+<#
+.DESCRIPTION
+ This cmdlet creates help documentation for code selections. It analyzes the code and generates
+ appropriate documentation comments. The key difference between functionnamefullload and functionnamepartialload is:
+
+ - functionnamefullload: Loads the complete function definition including all metadata, parameters,
+ body, and associated resources. This provides full context but may consume more memory and
+ processing time.
+
+ - functionnamepartialload: Loads only essential function information such as the function signature
+ and basic metadata. This is a lightweight operation suitable for scenarios where you only need
+ basic function details without the complete implementation.
+#>
function Register-IshRemoteMcpTool {
param(
- [Parameter(Mandatory)][AllowEmptyString()]
- [object[]]$FunctionName,
+ [AllowEmptyString()]
+ [object[]]$FunctionNameFullLoad,
+ [AllowEmptyString()]
+ [object[]]$FunctionNamePartialLoad,
[Switch]$DoNotCompress
)
- Write-IshRemoteLog -LogEntry @{ Level = 'Info'; Message = "Register-IshRemoteMcpTool for functions[$($FunctionName -join ', ')]" }
+ Write-IshRemoteLog -LogEntry @{ Level = 'Info'; Message = "Register-IshRemoteMcpTool for full help functions[$($FunctionNameFullLoad -join ',')] and partial functions[$($FunctionNamePartialLoad -join ',')]" }
+ # Combine both arrays, ensuring full load functions are included
+ $FunctionName = @()
+ if ($FunctionNameFullLoad) {
+ $FunctionName += $FunctionNameFullLoad
+ }
+ if ($FunctionNamePartialLoad) {
+ # Add partial load functions that are not already in full load
+ foreach ($partialFunc in $FunctionNamePartialLoad) {
+ if ($partialFunc -notin $FunctionNameFullLoad) {
+ $FunctionName += $partialFunc
+ }
+ }
+ }
+
$results = [ordered]@{}
foreach ($fn in $FunctionName) {
+ $fullLoad = $FunctionNameFullLoad -contains $fn
-
- Write-IshRemoteLog -LogEntry @{ Level = 'Verbose'; Message = "Register-IshRemoteMcpTool function[$fn]"; TargetFunction = $fn }
+ Write-IshRemoteLog -LogEntry @{ Level = 'Verbose'; Message = "Register-IshRemoteMcpTool function[$fn] fullLoad[$fullLoad]"; TargetFunction = $fn }
$CommandInfo = try { Get-Command -Name $fn -ErrorAction Stop } catch { $null }
if ($null -eq $CommandInfo) {
Write-IshRemoteLog -LogEntry @{ Level = 'Warn'; Message = "Register-IshRemoteMcpTool function[$fn] not found."; TargetFunction = $fn }
@@ -24,55 +53,92 @@ function Register-IshRemoteMcpTool {
Write-IshRemoteLog -LogEntry @{ Level = 'Verbose'; Message = "Register-IshRemoteMcpTool function[$($CommandInfo.Name)] extended help"; TargetFunction = $CommandInfo.Name }
- $help = Get-Help $CommandInfo.Name -Detailed
- # Prefer Description over Synopsis
- $description = $help.Description | Out-String
- if (-not $description) {
- $description = $help.Synopsis | Out-String
+ # Get help - for binary cmdlets in ISHRemote module, specify category to ensure correct help resolution
+ if ($CommandInfo -is [System.Management.Automation.CmdletInfo]) {
+ $help = Get-Help -Name $CommandInfo.Name -Category Cmdlet -Full
+ } else {
+ $help = Get-Help -Name $CommandInfo.Name -Full
+ }
+ # Prefer Description over Synopsis - access .Text property for reliable extraction
+ $description = ""
+ if ($null -ne $help.Description -and $help.Description.Count -gt 0) {
+ if ($help.Description -is [array]) {
+ $description = ($help.Description | ForEach-Object { if ($null -ne $_.Text) { $_.Text } }) -join "`n"
+ } elseif ($help.Description.Text) {
+ $description = $help.Description.Text
+ } else {
+ $description = ($help.Description | Out-String).Trim()
+ }
}
$description = $description.Trim()
+ if (-not $description -and $help.Synopsis) {
+ $description = $help.Synopsis.Trim()
+ }
if (-not $description) {
Write-IshRemoteLog -LogEntry @{ Level = 'Error'; Message = "Register-IshRemoteMcpTool function[$($CommandInfo.Name)] does not have a description (Synopsis or Description in comment-based help)."; TargetFunction = $CommandInfo.Name }
Write-Error "Register-IshRemoteMcpTool function[$($CommandInfo.Name)] does not have a description (Synopsis or Description in comment-based help). Aborting." -ErrorAction Stop
continue
}
- # Adding all syntax of parameter sets
- $description = $description + "`n`nThis PowerShell cmdlet has the following parameter sets to choose form where square brackets indicate optional parameters while the other parameters are mandatory:`n" + ($help.syntax | Out-String).Trim()
- # Adding all examples
- $description = $description + "`n`nThe PowerShell cmdlet has the following examples as inspiration:`n" + ($help.examples | Out-String).Trim()
+ if ($fullLoad) {
+ # Adding all syntax of parameter sets
+ $description = $description + "`n`nThis PowerShell cmdlet has the following parameter sets to choose form where square brackets indicate optional parameters while the other parameters are mandatory:`n" + ($help.syntax | Out-String).Trim()
+ }
+
+ if ($fullLoad) {
+ # Adding all examples
+ $description = $description + "`n`nThe PowerShell cmdlet has the following examples as inspiration:`n" + ($help.examples | Out-String).Trim()
+ }
- # Adding all parameters
- Write-IshRemoteLog -LogEntry @{ Level = 'Verbose'; Message = "Register-IshRemoteMcpTool function[$($CommandInfo.Name)] all parameters across parameter sets"; TargetFunction = $CommandInfo.Name }
- $Parameters = $CommandInfo.ParameterSets.Parameters |
- Where-Object { $_.Name -notmatch 'Verbose|Debug|ErrorAction|WarningAction|InformationAction|ErrorVariable|WarningVariable|InformationVariable|OutVariable|OutBuffer|PipelineVariable|WhatIf|Confirm|NoHyperLinkConversion|ProgressAction' } | Sort-Object -Property Name -Unique
$inputSchema = [ordered]@{
type = 'object'
properties = [ordered]@{}
required = @()
}
- foreach ($Parameter in $Parameters) {
- $typeName = $Parameter.ParameterType.Name.ToLower()
- switch -Regex ($typeName) {
- 'string' { $type = 'string' }
- 'int|int32|int64|double' { $type = 'number' }
- 'boolean' { $type = 'boolean' }
- 'switchparameter' { $type = 'boolean' }
- default { $type = 'string' }
- }
- try {
- $paramHelp = (Get-Help $CommandInfo.Name -Parameter $Parameter.Name -ErrorAction Stop).Description | Out-String
- }
- catch {
- Write-IshRemoteLog -LogEntry @{ Level = 'Warn'; Message = "Could not get help description for parameter '$($Parameter.Name)' on function '$($CommandInfo.Name)'. Often happens with forced import-module loading of PowerShell module while developing."; TargetFunction = $CommandInfo.Name; ParameterName = $Parameter.Name }
- $paramHelp = ""
+ if ($fullLoad) {
+ # Adding all parameters
+ Write-IshRemoteLog -LogEntry @{ Level = 'Verbose'; Message = "Register-IshRemoteMcpTool function[$($CommandInfo.Name)] all parameters across parameter sets"; TargetFunction = $CommandInfo.Name }
+ $Parameters = $CommandInfo.ParameterSets.Parameters |
+ Where-Object { $_.Name -notmatch 'Verbose|Debug|ErrorAction|WarningAction|InformationAction|ErrorVariable|WarningVariable|InformationVariable|OutVariable|OutBuffer|PipelineVariable|WhatIf|Confirm|NoHyperLinkConversion|ProgressAction' } | Sort-Object -Property Name -Unique
+ foreach ($Parameter in $Parameters) {
+ $typeName = $Parameter.ParameterType.Name.ToLower()
+ switch -Regex ($typeName) {
+ 'string' { $type = 'string' }
+ 'int|int32|int64|double' { $type = 'number' }
+ 'boolean' { $type = 'boolean' }
+ 'switchparameter' { $type = 'boolean' }
+ default { $type = 'string' }
+ }
+ try {
+ # Get parameter help - specify category for binary cmdlets
+ if ($CommandInfo -is [System.Management.Automation.CmdletInfo]) {
+ $paramHelpObj = Get-Help -Name $CommandInfo.Name -Parameter $Parameter.Name -Category Cmdlet -ErrorAction Stop
+ } else {
+ $paramHelpObj = Get-Help -Name $CommandInfo.Name -Parameter $Parameter.Name -ErrorAction Stop
+ }
+ $paramHelp = ""
+ if ($paramHelpObj.Description) {
+ if ($paramHelpObj.Description -is [array] -and $paramHelpObj.Description.Count -gt 0) {
+ $paramHelp = ($paramHelpObj.Description | ForEach-Object { if ($null -ne $_.Text) { $_.Text } }) -join "`n"
+ } elseif ($paramHelpObj.Description.Text) {
+ $paramHelp = $paramHelpObj.Description.Text
+ } else {
+ $paramHelp = ($paramHelpObj.Description | Out-String).Trim()
+ }
+ }
+ $paramHelp = $paramHelp.Trim()
+ }
+ catch {
+ Write-IshRemoteLog -LogEntry @{ Level = 'Warn'; Message = "Could not get help description for parameter '$($Parameter.Name)' on function '$($CommandInfo.Name)'. Often happens with forced import-module loading of PowerShell module while developing."; TargetFunction = $CommandInfo.Name; ParameterName = $Parameter.Name }
+ $paramHelp = ""
+ }
+ $paramHelp = if ($paramHelp) { $paramHelp.Trim() } else { "No description available." }
+ $inputSchema.properties[$Parameter.Name] = [ordered]@{ type = $type; description = $paramHelp }
+ # Only $help.syntax indicates if parameters are mandatory within parameter sets. E.g. setting Get-IshFolder -FolderId as required blocks other parameter sets where FolderId is not mandatory.
+ #if ($Parameter.IsMandatory) {
+ # $inputSchema.required += $Parameter.Name
+ #}
}
- $paramHelp = if ($paramHelp) { $paramHelp.Trim() } else { "No description available." }
- $inputSchema.properties[$Parameter.Name] = [ordered]@{ type = $type; description = $paramHelp }
- # Only $help.syntax indicates if parameters are mandatory within parameter sets. E.g. setting Get-IshFolder -FolderId as required blocks other parameter sets where FolderId is not mandatory.
- #if ($Parameter.IsMandatory) {
- # $inputSchema.required += $Parameter.Name
- #}
}
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Start-IshRemoteMcpServer.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Start-IshRemoteMcpServer.Tests.ps1
new file mode 100644
index 00000000..471b3b1c
--- /dev/null
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Start-IshRemoteMcpServer.Tests.ps1
@@ -0,0 +1,56 @@
+BeforeAll {
+ $cmdletName = "Start-IshRemoteMcpServer"
+ Write-Host ("`r`nLoading ISHRemote.PesterSetup.ps1 on PSVersion[" + $psversionTable.PSVersion + "] over BeforeAll-block for MyCommand[" + $cmdletName + "]...")
+ . (Join-Path (Split-Path -Parent $PSCommandPath) "\..\..\ISHRemote.PesterSetup.ps1")
+
+ function Write-IshRemoteLog {
+ param(
+ [Parameter(Mandatory = $true)]
+ [object]$LogEntry
+ )
+ }
+}
+
+Describe "Start-IshRemoteMcpServer" -Tags "Read" {
+ Context "Start-IshRemoteMcpServer with ActivateWhileLoop=false" {
+ BeforeEach {
+ Mock -ModuleName ISHRemote Write-IshRemoteLog { }
+ }
+ It "Validates CmdletsToRegister parameter is mandatory" {
+ { Start-IshRemoteMcpServer -ActivateWhileLoop $false -CmdletsToRegister @() } | Should -Throw
+ }
+ It "Starts server with CmdletsToRegister parameter" {
+ $cmdlets = @('Get-IshFolder', 'Set-IshFolder')
+ { Start-IshRemoteMcpServer -CmdletsToRegister $cmdlets -ActivateWhileLoop $false } | Should -Not -Throw
+ }
+ It "Starts server with both CmdletsToRegister and CmdletsToRegisterFullLoad parameters" {
+ $cmdlets = @('Get-IshFolder', 'Set-IshFolder')
+ $cmdletsFullLoad = @('Get-IshDocumentObj', 'Set-IshDocumentObj')
+ { Start-IshRemoteMcpServer -CmdletsToRegister $cmdlets -CmdletsToRegisterFullLoad $cmdletsFullLoad -ActivateWhileLoop $false } | Should -Not -Throw
+ }
+ It "Validates CmdletsToRegisterFullLoad defaults to Get-Help and New-IshSession" {
+ Mock -ModuleName ISHRemote Register-IshRemoteMcpTool { return "{}" }
+ $cmdlets = @('Get-IshFolder')
+ Start-IshRemoteMcpServer -CmdletsToRegister $cmdlets -ActivateWhileLoop $false
+ Should -Invoke -ModuleName ISHRemote Register-IshRemoteMcpTool -ParameterFilter {
+ $FunctionNameFullLoad -contains 'Get-Help' -and $FunctionNameFullLoad -contains 'New-IshSession'
+ }
+ }
+ It "Validates CmdletsToRegisterFullLoad only holds explicitly set, but not implicit Get-Help and New-IshSession" {
+ Mock -ModuleName ISHRemote Register-IshRemoteMcpTool { return "{}" }
+ $cmdlets = @('Get-IshFolder')
+ $cmdletsFullLoad = @('Get-IshDocumentObj', 'Set-IshDocumentObj')
+ Start-IshRemoteMcpServer -CmdletsToRegister $cmdlets -CmdletsToRegisterFullLoad $cmdletsFullLoad -ActivateWhileLoop $false
+ Should -Invoke -ModuleName ISHRemote Register-IshRemoteMcpTool -ParameterFilter {
+ $FunctionNameFullLoad -contains 'Get-IshDocumentObj' -and $FunctionNameFullLoad -contains 'Set-IshDocumentObj' -and -not ($FunctionNameFullLoad -contains 'Get-Help') -and -not ($FunctionNameFullLoad -contains 'New-IshSession')
+ }
+ }
+ It "Starts server with single cmdlet in CmdletsToRegister" {
+ { Start-IshRemoteMcpServer -CmdletsToRegister @('Get-IshFolder') -ActivateWhileLoop $false } | Should -Not -Throw
+ }
+ }
+}
+
+AfterAll {
+ Write-Host ("Running "+$cmdletName+" Test Data and Variables cleanup")
+}
\ No newline at end of file
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Start-IshRemoteMcpServer.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Start-IshRemoteMcpServer.ps1
index 5e3ae032..fa8d5d67 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Start-IshRemoteMcpServer.ps1
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Start-IshRemoteMcpServer.ps1
@@ -1,14 +1,52 @@
+<#
+.DESCRIPTION
+Starts an MCP server that exposes ISHRemote cmdlets as tools that can be called by MCP clients.
+This client-side MCP server is a hidden PowerShell (pwsh) session that holds below while loop to answer
+the LLM questions. These calls - after New-IshSession invoke in this hidden PowerShell process - could be
+direct queries to your chosen system or it can assist in writing scripts as every Mcp Tool is in essence
+an ISHRemote cmdlet. The server listens for JSON-RPC requests on standard input and writes responses
+to standard output.
+
+.PARAMETER CmdletsToRegister
+Array of cmdlet names to register for partial load so with Get-Help without syntax and examples.
+
+.PARAMETER CmdletsToRegisterFullLoad
+Array of cmdlet names to register for full load so with Get-Help -Detailed holding syntax and examples.
+When set explicitly, best to add @('Get-Help','New-IshSession').
+
+.PARAMETER LogFilePath
+Path to the log file where server activity will be recorded. Empty string or $null means logging is off.
+Example value could be "C:\TEMP\IshRemoteMcpServer.log".
+
+.PARAMETER ActivateWhileLoop
+Controls whether the server runs in a continuous loop. Set to $false for testing purposes. Defaults to $true.
+
+.EXAMPLE
+Start-IshRemoteMcpServer -CmdletsToRegister ((Get-Command -Module ISHRemote -ListImported -CommandType Cmdlet).Name)
+Starts the MCP server and registers the specified ISHRemote cmdlets as available tools. No log file.
+
+.EXAMPLE
+Start-IshRemoteMcpServer -CmdletsToRegisterFullLoad ((Get-Command -Module ISHRemote -ListImported -CommandType Cmdlet).Name) -LogFilePath "$env:TEMP\IshRemoteMcpServer.log"
+Starts the MCP server and registers the specified ISHRemote cmdlets as available tools using all information in Get-Help -Detailed including syntax and examples. Plus a log file is started.
+
+#>
function Start-IshRemoteMcpServer {
param(
[Parameter(Mandatory)]
[object[]]$CmdletsToRegister,
- [string]$LogFilePath = "C:\TEMP\IshRemoteMcpServer.log",
+ [object[]]$CmdletsToRegisterFullLoad = $null,
+ [string]$LogFilePath = $null,
[bool]$ActivateWhileLoop = $true
)
$script:logFilePath = $LogFilePath
- # TODO [SHOULD] Convert the tools list to JSON format, takes a while, so could be generated in ISHRemote at compile time
- $toolsListJson = Register-IshRemoteMcpTool $CmdletsToRegister
+ if ($CmdletsToRegisterFullLoad) {
+ # using incoming values explicitly
+ } else {
+ # else making sure these cmdlets get a full load
+ $CmdletsToRegisterFullLoad = @('Get-Help','New-IshSession')
+ }
+ $toolsListJson = Register-IshRemoteMcpTool -FunctionNameFullLoad $CmdletsToRegisterFullLoad -FunctionNamePartialLoad $CmdletsToRegister
$resourcesListJson = "" # Register-IshRemoteMcpResource
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Write-IshRemoteLog.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Write-IshRemoteLog.ps1
index 3ef5c003..153d7d29 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Write-IshRemoteLog.ps1
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Scripts/Public/Write-IshRemoteLog.ps1
@@ -1,10 +1,16 @@
+<#
+.DESCRIPTION
+Simple log function to the logFilePath when set in calling Start-ISHRemoteMcpServer.
+#>
function Write-IshRemoteLog {
param(
[Parameter(Mandatory = $true)]
[object]$LogEntry
)
- # Add a timestamp to the log entry
- $logObject = $LogEntry | Select-Object *, @{Name = 'Timestamp'; Expression = { (Get-Date -Format 'o') } }
- # Convert the object to a JSON string and append to the log file
- $logObject | ConvertTo-Json -Depth 10 -Compress | Add-Content -Path $script:logFilePath
+ if ($script:logFilePath) {
+ # Add a timestamp to the log entry
+ $logObject = $LogEntry | Select-Object *, @{Name = 'Timestamp'; Expression = { (Get-Date -Format 'o') } }
+ # Convert the object to a JSON string and append to the log file
+ $logObject | ConvertTo-Json -Depth 10 -Compress | Add-Content -Path $script:logFilePath
+ }
}
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Service References/Annotation25ServiceReference/Reference.cs b/Source/ISHRemote/Trisoft.ISHRemote/Service References/Annotation25ServiceReference/Reference.cs
index db81f547..e693db06 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/Service References/Annotation25ServiceReference/Reference.cs
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Service References/Annotation25ServiceReference/Reference.cs
@@ -1,4 +1,4 @@
-//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
//
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -211,7 +211,12 @@ public partial class AnnotationClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -147,7 +147,12 @@ public partial class ApplicationClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -280,7 +280,13 @@ public partial class BackgroundTaskClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -574,7 +574,13 @@ public partial class BaselineClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -1260,7 +1260,13 @@ public partial class DocumentObjClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -204,7 +204,13 @@ public partial class EDTClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -484,7 +484,13 @@ public partial class EventMonitorClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -424,7 +424,13 @@ public partial class FolderClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -258,7 +258,13 @@ public partial class ListOfValuesClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -177,7 +177,13 @@ public partial class MetadataBindingClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -274,7 +274,13 @@ public partial class OutputFormatClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -1017,7 +1017,13 @@ public partial class PublicationOutputClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -255,7 +255,13 @@ public partial class SearchClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -211,7 +211,13 @@ public partial class SettingsClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -368,7 +368,13 @@ public partial class TranslationJobClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -171,7 +171,13 @@ public partial class TranslationTemplateClient : System.ServiceModel.ClientBase<
public TranslationTemplateClient() {
}
-
+
+#if NET10_0_OR_GREATER
+ public TranslationTemplateClient(System.ServiceModel.Description.ServiceEndpoint endpoint) :
+ base(endpoint)
+ {
+ }
+#else
public TranslationTemplateClient(string endpointConfigurationName) :
base(endpointConfigurationName) {
}
@@ -183,6 +189,7 @@ public TranslationTemplateClient(string endpointConfigurationName, string remote
public TranslationTemplateClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
+#endif
public TranslationTemplateClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress) {
diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Service References/User25ServiceReference/Reference.cs b/Source/ISHRemote/Trisoft.ISHRemote/Service References/User25ServiceReference/Reference.cs
index 965cfddf..b3245853 100644
--- a/Source/ISHRemote/Trisoft.ISHRemote/Service References/User25ServiceReference/Reference.cs
+++ b/Source/ISHRemote/Trisoft.ISHRemote/Service References/User25ServiceReference/Reference.cs
@@ -1,4 +1,4 @@
-//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
//
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -289,7 +289,13 @@ public partial class UserClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -201,7 +201,13 @@ public partial class UserGroupClient : System.ServiceModel.ClientBase
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -201,7 +201,13 @@ public partial class UserRoleClient : System.ServiceModel.ClientBase
- net48;net6.0
-
+ net48;net6.0;net10.0
true
true
@@ -14,26 +13,61 @@
-
-
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -99,8 +133,11 @@
$(ProjectDir)bin/$(Configuration)/ISHRemote
+
+
+
@@ -126,10 +163,11 @@
$(ProjectDir)bin/$(Configuration)
$(ProjectDir)bin/$(Configuration)/ISHRemote/$(TargetFramework)
-
+
-
-
+
+
+
@@ -138,6 +176,7 @@
+
@@ -162,3 +201,4 @@
+