From df37b968f3f28076df211b517196793f59d3670c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B6tz=20Jensen?= Date: Thu, 9 Oct 2025 09:07:01 +0200 Subject: [PATCH 1/2] Feature: Add scripts to handle the config store of credentials and starting SSMS No docs in place yet... --- d365fo.tools/d365fo.tools.psd1 | 5 ++ .../get-d365udedatabasecredential.ps1 | 45 +++++++++++ .../set-d365udedatabasecredential.ps1 | 75 +++++++++++++++++++ .../functions/start-d365udessmssession.ps1 | 37 +++++++++ .../internal/configurations/configuration.ps1 | 2 + d365fo.tools/internal/tepp/assignment.ps1 | 5 ++ .../internal/tepp/configurations.tepp.ps1 | 3 + 7 files changed, 172 insertions(+) create mode 100644 d365fo.tools/functions/get-d365udedatabasecredential.ps1 create mode 100644 d365fo.tools/functions/set-d365udedatabasecredential.ps1 create mode 100644 d365fo.tools/functions/start-d365udessmssession.ps1 create mode 100644 d365fo.tools/internal/tepp/configurations.tepp.ps1 diff --git a/d365fo.tools/d365fo.tools.psd1 b/d365fo.tools/d365fo.tools.psd1 index 8f7981cd..b138df48 100644 --- a/d365fo.tools/d365fo.tools.psd1 +++ b/d365fo.tools/d365fo.tools.psd1 @@ -168,6 +168,7 @@ 'Get-D365TfsUri', 'Get-D365TfsWorkspace', + 'Get-D365UdeDatabaseCredential', 'Get-D365Url', 'Get-D365User', 'Get-D365UserAuthenticationDetail', @@ -310,6 +311,8 @@ 'Set-D365SqlPackagePath', 'Set-D365SysAdmin', + 'Set-D365UdeDatabaseCredential', + 'Set-D365WebConfigDatabase', 'Set-D365WebServerType', @@ -323,6 +326,8 @@ 'Start-D365EnvironmentV2', 'Start-D365EventTrace', + 'Start-D365UdeSsmsSession', + 'Stop-D365Environment', 'Stop-D365EventTrace', diff --git a/d365fo.tools/functions/get-d365udedatabasecredential.ps1 b/d365fo.tools/functions/get-d365udedatabasecredential.ps1 new file mode 100644 index 00000000..c8478952 --- /dev/null +++ b/d365fo.tools/functions/get-d365udedatabasecredential.ps1 @@ -0,0 +1,45 @@ +function Get-D365UdeDatabaseCredential { + [CmdletBinding()] + param ( + [Alias("Name")] + [string] $Id = "*", + + [switch] $Latest, + + [switch] $ShowPassword + ) + + begin { + if ($null -eq (Get-Module TUN.CredentialManager -ListAvailable)) { + Write-PSFMessage -Level Host -Message "This cmdlet needs the TUN.CredentialManager module. Please install it from the PowerShell Gallery with Install-Module -Name TUN.CredentialManager and try again." + Stop-PSFFunction -Message "Stopping because the TUN.CredentialManager module is not available." + + return + } + + if (Test-PSFFunctionInterrupt) { return } + + Import-Module TUN.CredentialManager + } + + process { + if (Test-PSFFunctionInterrupt) { return } + + $credentials = [hashtable](Get-PSFConfigValue -FullName "d365fo.tools.ude.credentials") + + $col = @( + foreach ($key in $credentials.Keys) { + if ($key -like $Id) { + $credentials[$key] + } + } + ) + + if ($ShowPassword) { + $col + } + else { + $col | Select-PSFObject -Property * -ExcludeProperty Password + } + } +} \ No newline at end of file diff --git a/d365fo.tools/functions/set-d365udedatabasecredential.ps1 b/d365fo.tools/functions/set-d365udedatabasecredential.ps1 new file mode 100644 index 00000000..049262db --- /dev/null +++ b/d365fo.tools/functions/set-d365udedatabasecredential.ps1 @@ -0,0 +1,75 @@ + +function Set-D365UdeDatabaseCredential { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")] + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string] $Id, + + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] + [Alias("DatabaseServer")] + [Alias("ServerName")] + [string] $Server, + + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] + [Alias("DatabaseName")] + [string] $Database, + + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] + [Alias("SqlUser")] + [string] $Username, + + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] + [Alias("SqlPwd")] + [string] $Password, + + [Alias("SQLJITExpirationTime")] + [datetime] $ValidUntil = (Get-Date).AddHours(8) + ) + + begin { + if ($null -eq (Get-Module TUN.CredentialManager -ListAvailable)) { + Write-PSFMessage -Level Host -Message "This cmdlet needs the TUN.CredentialManager module. Please install it from the PowerShell Gallery with Install-Module -Name TUN.CredentialManager and try again." + Stop-PSFFunction -Message "Stopping because the TUN.CredentialManager module is not available." + + return + } + + if (Test-PSFFunctionInterrupt) { return } + + Import-Module TUN.CredentialManager + } + + process { + if (Test-PSFFunctionInterrupt) { return } + + $SqlServerGUID = "8c91a03d-f9b4-46c0-a305-b5dcc79ff907" + + $details = [PSCustomObject][ordered]@{ + Id = $Id + Server = $($Server) + Database = $($Database) + Username = $($Username) + Password = $($Password) + Expiration = $($ValidUntil.ToString("s")) + } + + # Setting up the SQL Server Management Studio (SSMS) Credential for version 20 - 21 + 20, 21 | ForEach-Object { + New-StoredCredential ` + -UserName $Username ` + -Password $Password ` + -Persist LocalMachine ` + -Target "Microsoft:SSMS:$($_):$($Server):$($Username):$($SqlServerGUID):1" > $null + } + + $credentials = [hashtable](Get-PSFConfigValue -FullName "d365fo.tools.ude.credentials") + $credentials."$Id" = $details + + Set-PSFConfig -FullName "d365fo.tools.ude.credentials" -Value $credentials + Register-PSFConfig -FullName "d365fo.tools.ude.credentials" -Scope UserDefault + + Get-D365UdeDatabaseCredential -Id $Id + } +} \ No newline at end of file diff --git a/d365fo.tools/functions/start-d365udessmssession.ps1 b/d365fo.tools/functions/start-d365udessmssession.ps1 new file mode 100644 index 00000000..c8f6acee --- /dev/null +++ b/d365fo.tools/functions/start-d365udessmssession.ps1 @@ -0,0 +1,37 @@ + +function Start-D365UdeSsmsSession { + param ( + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] + [string] $Id, + + [ValidateSet(20, 21)] + [int] $Version = 20 + ) + + process { + $udeCreds = Get-D365UdeDatabaseCredential -Id $Id + + if ($null -eq $udeCreds) { + Write-PSFMessage -Level Host -Message "No credential found for Id '$Id'. Please check the Id and try again." + Stop-PSFFunction -Message "Stopping because no matching credential was found." + + return + } + + if (Test-PSFFunctionInterrupt) { return } + + # Hack to get the SSMS executable path from the registry + $ssmsInstalled = Get-ChildItem ` + -Path Registry::HKEY_CLASSES_ROOT\ssms.*\shell\Open\Command | ` + Select-Object -ExpandProperty PsPath | ` + ForEach-Object { (Get-ItemProperty -Path $_)."(Default)" } | ` + Select-String -Pattern '^[\"]?(.*ssms\.exe)["]?\s*"%1"' | ` + ForEach-Object { $_.Matches.Groups[1].Value } | Select-Object -Unique + + $executablePath = $ssmsInstalled | ` + Where-Object { $_ -match "Microsoft SQL Server Management Studio $($Version)\b" } | ` + Select-Object -First 1 + + & $executablePath -S $($udeCreds.Server) -d $($udeCreds.Database) -U $($udeCreds.Username) + } +} \ No newline at end of file diff --git a/d365fo.tools/internal/configurations/configuration.ps1 b/d365fo.tools/internal/configurations/configuration.ps1 index 40fc2dd8..c0522b64 100644 --- a/d365fo.tools/internal/configurations/configuration.ps1 +++ b/d365fo.tools/internal/configurations/configuration.ps1 @@ -40,3 +40,5 @@ Set-PSFConfig -FullName "d365fo.tools.path.rsatplayback" -Value "C:\Users\$($env Set-PSFConfig -FullName "d365fo.tools.path.azcopy" -Value "C:\temp\d365fo.tools\AzCopy\AzCopy.exe" -Initialize -Description "Path to the default location where AzCopy.exe is located." Set-PSFConfig -FullName "d365fo.tools.path.nuget" -Value "C:\temp\d365fo.tools\nuget\nuget.exe" -Initialize -Description "Path to the default location where nuget.exe is located." + +Set-PSFConfig -FullName "d365fo.tools.ude.credentials" -Value @{} -Initialize -Description "Object that stores different Ude Database credentials and their details." diff --git a/d365fo.tools/internal/tepp/assignment.ps1 b/d365fo.tools/internal/tepp/assignment.ps1 index 6b7f4237..29acdd4c 100644 --- a/d365fo.tools/internal/tepp/assignment.ps1 +++ b/d365fo.tools/internal/tepp/assignment.ps1 @@ -34,3 +34,8 @@ Register-PSFTeppArgumentCompleter -Command Add-D365BroadcastMessageConfig -Param #Event Trace Register-PSFTeppArgumentCompleter -Command Start-D365EventTrace -Parameter ProviderName -Name d365fo.tools.event.trace.providers Register-PSFTeppArgumentCompleter -Command Start-D365EventTrace -Parameter OutputFormat -Name d365fo.tools.event.trace.format.options + +#UDE Credentials +Register-PSFTeppArgumentCompleter -Command Get-D365UdeDatabaseCredential -Parameter Id -Name "d365fo.tools.ude.credentials" +Register-PSFTeppArgumentCompleter -Command Start-D365UdeSsmsSession -Parameter Id -Name "d365fo.tools.ude.credentials" +Register-PSFTeppArgumentCompleter -Command Set-D365UdeDatabaseCredential -Parameter Id -Name "d365fo.tools.ude.credentials" diff --git a/d365fo.tools/internal/tepp/configurations.tepp.ps1 b/d365fo.tools/internal/tepp/configurations.tepp.ps1 new file mode 100644 index 00000000..e1d383c5 --- /dev/null +++ b/d365fo.tools/internal/tepp/configurations.tepp.ps1 @@ -0,0 +1,3 @@ +$scriptBlock = { Get-D365UdeDatabaseCredential | Sort-Object Id | Select-Object -ExpandProperty Id } + +Register-PSFTeppScriptblock -Name "d365fo.tools.ude.credentials" -ScriptBlock $scriptBlock -Mode Simple From 59a5e4b5794a2c3455b5f4f6b9673da145f0b250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B6tz=20Jensen?= Date: Thu, 9 Oct 2025 09:14:55 +0200 Subject: [PATCH 2/2] Fix: Adopt suggestions from Copilot --- d365fo.tools/functions/get-d365udedatabasecredential.ps1 | 2 -- d365fo.tools/functions/start-d365udessmssession.ps1 | 8 ++++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/d365fo.tools/functions/get-d365udedatabasecredential.ps1 b/d365fo.tools/functions/get-d365udedatabasecredential.ps1 index c8478952..84dd85f2 100644 --- a/d365fo.tools/functions/get-d365udedatabasecredential.ps1 +++ b/d365fo.tools/functions/get-d365udedatabasecredential.ps1 @@ -4,8 +4,6 @@ [Alias("Name")] [string] $Id = "*", - [switch] $Latest, - [switch] $ShowPassword ) diff --git a/d365fo.tools/functions/start-d365udessmssession.ps1 b/d365fo.tools/functions/start-d365udessmssession.ps1 index c8f6acee..480b0b65 100644 --- a/d365fo.tools/functions/start-d365udessmssession.ps1 +++ b/d365fo.tools/functions/start-d365udessmssession.ps1 @@ -18,8 +18,6 @@ function Start-D365UdeSsmsSession { return } - if (Test-PSFFunctionInterrupt) { return } - # Hack to get the SSMS executable path from the registry $ssmsInstalled = Get-ChildItem ` -Path Registry::HKEY_CLASSES_ROOT\ssms.*\shell\Open\Command | ` @@ -32,6 +30,12 @@ function Start-D365UdeSsmsSession { Where-Object { $_ -match "Microsoft SQL Server Management Studio $($Version)\b" } | ` Select-Object -First 1 + if ([string]::IsNullOrWhiteSpace($executablePath)) { + Write-PSFMessage -Level Host -Message "Could not find a valid SSMS executable for version $Version. Please ensure the version is installed or try a different version." + Stop-PSFFunction -Message "Stopping because SSMS executable was not found." + return + } + & $executablePath -S $($udeCreds.Server) -d $($udeCreds.Database) -U $($udeCreds.Username) } } \ No newline at end of file