diff --git a/README.md b/README.md index dbdb3490c..819906181 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ For more advanced scenarios including metadata and customisations, see the [docu - [View Management](docs/core-concepts/view-management.md) - Create, update, and manage system and personal views - [App Module Management](docs/core-concepts/app-module-management.md) - Create, update, and manage model-driven apps - [Environment Variables and Connection References](docs/core-concepts/environment-variables-connection-references.md) - Managing configuration and connections +- [URL Generation](docs/core-concepts/url-generation.md) - Generate URLs for records, maker portal, and admin center - [Plugin Management](docs/core-concepts/plugin-management.md) - Manage plugins including dynamic plugin assemblies (compile C# on-the-fly), traditional plugin assemblies, plugin steps, and images - [Solution Management](docs/core-concepts/solution-management.md) - Import, export, and manage solutions - [Solution Component Management](docs/core-concepts/solution-component-management.md) - Managing individual components within solutions diff --git a/Rnwood.Dataverse.Data.PowerShell.Cmdlets/Commands/GetDataverseAdminPortalUrlCmdlet.cs b/Rnwood.Dataverse.Data.PowerShell.Cmdlets/Commands/GetDataverseAdminPortalUrlCmdlet.cs new file mode 100644 index 000000000..1ddaddf5b --- /dev/null +++ b/Rnwood.Dataverse.Data.PowerShell.Cmdlets/Commands/GetDataverseAdminPortalUrlCmdlet.cs @@ -0,0 +1,40 @@ +using System; +using System.Management.Automation; +using Microsoft.PowerPlatform.Dataverse.Client; + +namespace Rnwood.Dataverse.Data.PowerShell.Commands +{ + /// + /// Generates a URL to open the Power Platform Admin Center for the current environment. + /// + [Cmdlet(VerbsCommon.Get, "DataverseAdminPortalUrl")] + [OutputType(typeof(string))] + public class GetDataverseAdminPortalUrlCmdlet : OrganizationServiceCmdlet + { + /// + /// Processes the cmdlet to generate the admin portal URL. + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + if (Connection == null) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("No connection provided. Use -Connection parameter or set a default connection."), + "NoConnection", + ErrorCategory.InvalidOperation, + null)); + return; + } + + // Use ConnectedOrgId directly - avoids an extra WhoAmI network call + Guid orgId = Connection.ConnectedOrgId; + + // Build the admin portal URL for the specific environment + string url = $"https://admin.powerplatform.microsoft.com/environments/{orgId:D}/hub"; + + WriteObject(url); + } + } +} diff --git a/Rnwood.Dataverse.Data.PowerShell.Cmdlets/Commands/GetDataverseMakerPortalUrlCmdlet.cs b/Rnwood.Dataverse.Data.PowerShell.Cmdlets/Commands/GetDataverseMakerPortalUrlCmdlet.cs new file mode 100644 index 000000000..e2381c5a4 --- /dev/null +++ b/Rnwood.Dataverse.Data.PowerShell.Cmdlets/Commands/GetDataverseMakerPortalUrlCmdlet.cs @@ -0,0 +1,58 @@ +using System; +using System.Management.Automation; +using Microsoft.PowerPlatform.Dataverse.Client; + +namespace Rnwood.Dataverse.Data.PowerShell.Commands +{ + /// + /// Generates a URL to open the Power Apps Maker Portal for the current environment. + /// + [Cmdlet(VerbsCommon.Get, "DataverseMakerPortalUrl")] + [OutputType(typeof(string))] + public class GetDataverseMakerPortalUrlCmdlet : OrganizationServiceCmdlet + { + /// + /// Gets or sets the logical name of the table to open in the maker portal. + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, HelpMessage = "Logical name of the table to open in the maker portal (e.g., 'account', 'contact').")] + [Alias("EntityName", "LogicalName")] + public string TableName { get; set; } + + /// + /// Processes the cmdlet to generate the maker portal URL. + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + if (Connection == null) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("No connection provided. Use -Connection parameter or set a default connection."), + "NoConnection", + ErrorCategory.InvalidOperation, + null)); + return; + } + + // Use ConnectedOrgId directly - avoids an extra WhoAmI network call + Guid orgId = Connection.ConnectedOrgId; + + // Build the maker portal URL + string baseUrl = "https://make.powerapps.com"; + string url = $"{baseUrl}/environments/{orgId:D}"; + + // If table name is provided, navigate to that table + if (!string.IsNullOrEmpty(TableName)) + { + url += $"/entities/entity/{TableName}"; + } + else + { + url += "/home"; + } + + WriteObject(url); + } + } +} diff --git a/Rnwood.Dataverse.Data.PowerShell.Cmdlets/Commands/GetDataverseRecordUrlCmdlet.cs b/Rnwood.Dataverse.Data.PowerShell.Cmdlets/Commands/GetDataverseRecordUrlCmdlet.cs new file mode 100644 index 000000000..4f1583625 --- /dev/null +++ b/Rnwood.Dataverse.Data.PowerShell.Cmdlets/Commands/GetDataverseRecordUrlCmdlet.cs @@ -0,0 +1,145 @@ +using System; +using System.Linq; +using System.Management.Automation; +using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; + +namespace Rnwood.Dataverse.Data.PowerShell.Commands +{ + /// + /// Generates a URL to open a record in the Dataverse web interface. + /// + [Cmdlet(VerbsCommon.Get, "DataverseRecordUrl", DefaultParameterSetName = "ByAppUniqueName")] + [OutputType(typeof(string))] + public class GetDataverseRecordUrlCmdlet : OrganizationServiceCmdlet + { + /// + /// Gets or sets the logical name of the table (entity). + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "Logical name of the table (e.g., 'account', 'contact').")] + [Alias("EntityName", "LogicalName")] + public string TableName { get; set; } + + /// + /// Gets or sets the ID of the record. If not provided, generates a URL for creating a new record. + /// + [Parameter(Mandatory = false, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "ID of the record. If not provided, generates a URL for creating a new record.")] + [Alias("RecordId")] + public Guid? Id { get; set; } + + /// + /// Gets or sets the unique name of the app to open the record in a specific model-driven app. + /// + [Parameter(Mandatory = false, ParameterSetName = "ByAppUniqueName", ValueFromPipelineByPropertyName = true, HelpMessage = "Unique name of the app to open the record in a specific model-driven app. The app ID will be looked up (including unpublished apps).")] + [Alias("UniqueName")] + public string AppUniqueName { get; set; } + + /// + /// Gets or sets the app ID to open the record in a specific model-driven app. + /// + [Parameter(Mandatory = false, ParameterSetName = "ByAppId", HelpMessage = "App ID to open the record in a specific model-driven app.")] + public Guid? AppId { get; set; } + + /// + /// Gets or sets the form ID to open a specific form. + /// + [Parameter(Mandatory = false, HelpMessage = "Form ID to open a specific form for the record.")] + public Guid? FormId { get; set; } + + /// + /// Processes the cmdlet to generate the record URL. + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + if (Connection == null) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("No connection provided. Use -Connection parameter or set a default connection."), + "NoConnection", + ErrorCategory.InvalidOperation, + null)); + return; + } + + // Extract the base URL from the connection using the WebApplication endpoint + string baseUrl = null; + if (Connection.ConnectedOrgPublishedEndpoints?.ContainsKey(Microsoft.Xrm.Sdk.Discovery.EndpointType.WebApplication) == true) + { + baseUrl = Connection.ConnectedOrgPublishedEndpoints[Microsoft.Xrm.Sdk.Discovery.EndpointType.WebApplication]?.ToString(); + } + + if (string.IsNullOrEmpty(baseUrl)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("Unable to determine organization URL from connection."), + "InvalidConnection", + ErrorCategory.InvalidOperation, + null)); + return; + } + + // Remove trailing slash + baseUrl = baseUrl.TrimEnd('/'); + + // Resolve AppId from AppUniqueName if provided + Guid? resolvedAppId = AppId; + if (!string.IsNullOrEmpty(AppUniqueName)) + { + WriteVerbose($"Looking up app module by unique name: {AppUniqueName}"); + + var query = new QueryExpression("appmodule") + { + ColumnSet = new ColumnSet("appmoduleid"), + Criteria = new FilterExpression() + }; + query.Criteria.AddCondition("uniquename", ConditionOperator.Equal, AppUniqueName); + + // Query including unpublished apps + var appModules = QueryHelpers.ExecuteQueryWithPaging(query, Connection, WriteVerbose, unpublished: true); + var appModule = appModules.FirstOrDefault(); + + if (appModule == null) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException($"App module with unique name '{AppUniqueName}' not found."), + "AppModuleNotFound", + ErrorCategory.ObjectNotFound, + AppUniqueName)); + return; + } + + resolvedAppId = appModule.Id; + WriteVerbose($"Resolved app module ID: {resolvedAppId}"); + } + + // Build the URL + string url; + if (Id.HasValue) + { + // URL for existing record + url = $"{baseUrl}/main.aspx?etn={TableName}&id={Id.Value:D}&pagetype=entityrecord"; + } + else + { + // URL for creating new record + url = $"{baseUrl}/main.aspx?etn={TableName}&pagetype=entityrecord"; + } + + // Add optional parameters + if (resolvedAppId.HasValue) + { + url += $"&appid={resolvedAppId.Value:D}"; + } + + if (FormId.HasValue) + { + url += $"&formid={FormId.Value:D}"; + } + + WriteObject(url); + } + } +} diff --git a/Rnwood.Dataverse.Data.PowerShell.E2ETests/Urls/UrlCmdletTests.cs b/Rnwood.Dataverse.Data.PowerShell.E2ETests/Urls/UrlCmdletTests.cs new file mode 100644 index 000000000..6f5b3f904 --- /dev/null +++ b/Rnwood.Dataverse.Data.PowerShell.E2ETests/Urls/UrlCmdletTests.cs @@ -0,0 +1,200 @@ +using FluentAssertions; +using Rnwood.Dataverse.Data.PowerShell.E2ETests.Infrastructure; +using Xunit; + +namespace Rnwood.Dataverse.Data.PowerShell.E2ETests.Urls +{ + /// + /// E2E tests for URL generation cmdlets: Get-DataverseRecordUrl, Get-DataverseMakerPortalUrl, Get-DataverseAdminPortalUrl. + /// + public class UrlCmdletTests : E2ETestBase + { + [Fact] + public void GetDataverseRecordUrl_ExistingRecord_ReturnsCorrectUrl() + { + var script = GetConnectionScript(@" +$ErrorActionPreference = 'Stop' + +# Get an existing contact record +$contacts = Get-DataverseRecord -Connection $connection -TableName contact -Top 1 -Columns contactid +if (-not $contacts) { throw 'No contact records found for testing' } +$contactId = $contacts.contactid + +$url = Get-DataverseRecordUrl -Connection $connection -TableName contact -Id $contactId + +Write-Host ""URL: $url"" + +# Validate URL format +$orgUrl = $connection.ConnectedOrgPublishedEndpoints['WebApplication'].TrimEnd('/') +if (-not $url.StartsWith($orgUrl)) { throw ""URL does not start with org URL. Expected start: $orgUrl, Got: $url"" } +if ($url -notmatch 'main\.aspx') { throw ""URL does not contain main.aspx"" } +if ($url -notmatch 'etn=contact') { throw ""URL does not contain etn=contact"" } +if ($url -notmatch ""id=$contactId"") { throw ""URL does not contain record ID"" } +if ($url -notmatch 'pagetype=entityrecord') { throw ""URL does not contain pagetype=entityrecord"" } + +Write-Host 'SUCCESS' +"); + var result = RunScript(script); + result.StandardOutput.Should().Contain("SUCCESS", because: result.GetFullOutput()); + } + + [Fact] + public void GetDataverseRecordUrl_NewRecord_ReturnsUrlWithoutId() + { + var script = GetConnectionScript(@" +$ErrorActionPreference = 'Stop' + +$url = Get-DataverseRecordUrl -Connection $connection -TableName account + +Write-Host ""URL: $url"" + +$orgUrl = $connection.ConnectedOrgPublishedEndpoints['WebApplication'].TrimEnd('/') +if (-not $url.StartsWith($orgUrl)) { throw ""URL does not start with org URL"" } +if ($url -notmatch 'etn=account') { throw ""URL does not contain etn=account"" } +if ($url -match '&id=') { throw ""URL should not contain an id parameter"" } +if ($url -notmatch 'pagetype=entityrecord') { throw ""URL does not contain pagetype=entityrecord"" } + +Write-Host 'SUCCESS' +"); + var result = RunScript(script); + result.StandardOutput.Should().Contain("SUCCESS", because: result.GetFullOutput()); + } + + [Fact] + public void GetDataverseRecordUrl_WithAppUniqueName_ResolvesAppIdAndIncludesInUrl() + { + var script = GetConnectionScript(@" +$ErrorActionPreference = 'Stop' + +# Get an existing app module and a contact record +$apps = Get-DataverseRecord -Connection $connection -TableName appmodule -Columns appmoduleid,uniquename -Top 1 +if (-not $apps) { throw 'No app modules found for testing' } +$app = $apps[0] +Write-Host ""Using app: $($app.uniquename) / $($app.appmoduleid)"" + +$contacts = Get-DataverseRecord -Connection $connection -TableName contact -Top 1 -Columns contactid +$contactId = $contacts.contactid + +$url = Get-DataverseRecordUrl -Connection $connection -TableName contact -Id $contactId -AppUniqueName $app.uniquename + +Write-Host ""URL: $url"" + +if ($url -notmatch ""appid=$($app.appmoduleid)"") { throw ""URL does not contain expected appid $($app.appmoduleid). URL: $url"" } + +Write-Host 'SUCCESS' +"); + var result = RunScript(script); + result.StandardOutput.Should().Contain("SUCCESS", because: result.GetFullOutput()); + } + + [Fact] + public void GetDataverseRecordUrl_WithAppId_IncludesAppIdInUrl() + { + var script = GetConnectionScript(@" +$ErrorActionPreference = 'Stop' + +$apps = Get-DataverseRecord -Connection $connection -TableName appmodule -Columns appmoduleid -Top 1 +if (-not $apps) { throw 'No app modules found for testing' } +$appId = $apps[0].appmoduleid + +$contacts = Get-DataverseRecord -Connection $connection -TableName contact -Top 1 -Columns contactid +$contactId = $contacts.contactid + +$url = Get-DataverseRecordUrl -Connection $connection -TableName contact -Id $contactId -AppId $appId + +Write-Host ""URL: $url"" + +if ($url -notmatch ""appid=$appId"") { throw ""URL does not contain appid. URL: $url"" } + +Write-Host 'SUCCESS' +"); + var result = RunScript(script); + result.StandardOutput.Should().Contain("SUCCESS", because: result.GetFullOutput()); + } + + [Fact] + public void GetDataverseRecordUrl_WithInvalidAppUniqueName_ThrowsError() + { + var script = GetConnectionScript(@" +$ErrorActionPreference = 'Stop' + +try { + Get-DataverseRecordUrl -Connection $connection -TableName contact -AppUniqueName 'nonexistent_app_xyz_12345' + throw 'Should have thrown an error for non-existent app' +} catch { + if ($_.Exception.Message -notmatch 'nonexistent_app_xyz_12345') { + throw ""Unexpected error message: $($_.Exception.Message)"" + } + Write-Host ""Correctly threw error for missing app"" +} + +Write-Host 'SUCCESS' +"); + var result = RunScript(script); + result.StandardOutput.Should().Contain("SUCCESS", because: result.GetFullOutput()); + } + + [Fact] + public void GetDataverseMakerPortalUrl_NoParameters_ReturnsHomePageUrl() + { + var script = GetConnectionScript(@" +$ErrorActionPreference = 'Stop' + +$url = Get-DataverseMakerPortalUrl -Connection $connection + +Write-Host ""URL: $url"" + +$orgId = $connection.ConnectedOrgId.ToString('D') +if (-not $url.StartsWith('https://make.powerapps.com/environments/')) { throw ""URL does not start with expected prefix: $url"" } +if ($url -notmatch $orgId) { throw ""URL does not contain org ID $orgId. URL: $url"" } +if (-not $url.EndsWith('/home')) { throw ""URL does not end with /home: $url"" } + +Write-Host 'SUCCESS' +"); + var result = RunScript(script); + result.StandardOutput.Should().Contain("SUCCESS", because: result.GetFullOutput()); + } + + [Fact] + public void GetDataverseMakerPortalUrl_WithTableName_ReturnsTableContextUrl() + { + var script = GetConnectionScript(@" +$ErrorActionPreference = 'Stop' + +$url = Get-DataverseMakerPortalUrl -Connection $connection -TableName contact + +Write-Host ""URL: $url"" + +$orgId = $connection.ConnectedOrgId.ToString('D') +if (-not $url.StartsWith('https://make.powerapps.com/environments/')) { throw ""URL does not start with expected prefix: $url"" } +if ($url -notmatch $orgId) { throw ""URL does not contain org ID $orgId. URL: $url"" } +if (-not $url.EndsWith('/entities/entity/contact')) { throw ""URL does not end with table path: $url"" } + +Write-Host 'SUCCESS' +"); + var result = RunScript(script); + result.StandardOutput.Should().Contain("SUCCESS", because: result.GetFullOutput()); + } + + [Fact] + public void GetDataverseAdminPortalUrl_ReturnsEnvironmentHubUrl() + { + var script = GetConnectionScript(@" +$ErrorActionPreference = 'Stop' + +$url = Get-DataverseAdminPortalUrl -Connection $connection + +Write-Host ""URL: $url"" + +$orgId = $connection.ConnectedOrgId.ToString('D') +if (-not $url.StartsWith('https://admin.powerplatform.microsoft.com/environments/')) { throw ""URL does not start with expected prefix: $url"" } +if ($url -notmatch $orgId) { throw ""URL does not contain org ID $orgId. URL: $url"" } +if (-not $url.EndsWith('/hub')) { throw ""URL does not end with /hub: $url"" } + +Write-Host 'SUCCESS' +"); + var result = RunScript(script); + result.StandardOutput.Should().Contain("SUCCESS", because: result.GetFullOutput()); + } + } +} diff --git a/Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseAdminPortalUrl.md b/Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseAdminPortalUrl.md new file mode 100644 index 000000000..a539ec4ba --- /dev/null +++ b/Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseAdminPortalUrl.md @@ -0,0 +1,103 @@ +--- +external help file: Rnwood.Dataverse.Data.PowerShell.Cmdlets.dll-Help.xml +Module Name: Rnwood.Dataverse.Data.PowerShell +online version: +schema: 2.0.0 +--- + +# Get-DataverseAdminPortalUrl + +## SYNOPSIS +Generates a URL to open the Power Platform Admin Center for the current environment. + +## SYNTAX + +``` +Get-DataverseAdminPortalUrl [-Connection ] [-ProgressAction ] + [] +``` + +## DESCRIPTION + +This cmdlet generates a URL that opens the Power Platform Admin Center for the Dataverse environment associated with the current connection. + +The Admin Center is where administrators can: +- Manage environment settings +- View environment details +- Configure environment resources +- Monitor environment health +- Manage environment access and security + +## EXAMPLES + +### Example 1: Get URL for environment in Admin Center +```powershell +PS C:\> Get-DataverseAdminPortalUrl -Connection $c +``` + +Returns a URL to open the Admin Center for the connected environment. + +### Example 2: Open Admin Center directly in browser +```powershell +PS C:\> Start-Process (Get-DataverseAdminPortalUrl -Connection $c) +``` + +Opens the Admin Center for the environment directly in the default web browser. + +### Example 3: Get Admin URLs for multiple connections +```powershell +PS C:\> $connections | ForEach-Object { Get-DataverseAdminPortalUrl -Connection $_ } +``` + +Generates Admin Center URLs for multiple connections. + +## PARAMETERS + +### -Connection +DataverseConnection instance obtained from Get-DataverseConnection cmdlet, or string specifying Dataverse organization URL (e.g. http://server.com/MyOrg/). If not provided, uses the default connection set via Get-DataverseConnection -SetAsDefault. + +```yaml +Type: ServiceClient +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### None +## OUTPUTS + +### System.String +## NOTES + +The Admin Center URL format is: https://admin.powerplatform.microsoft.com/environments/{environmentId}/hub + +Administrative permissions may be required to access the Admin Center. + +## RELATED LINKS + +[Power Platform Admin Center](https://admin.powerplatform.microsoft.com) diff --git a/Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseMakerPortalUrl.md b/Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseMakerPortalUrl.md new file mode 100644 index 000000000..295d68b35 --- /dev/null +++ b/Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseMakerPortalUrl.md @@ -0,0 +1,127 @@ +--- +external help file: Rnwood.Dataverse.Data.PowerShell.Cmdlets.dll-Help.xml +Module Name: Rnwood.Dataverse.Data.PowerShell +online version: +schema: 2.0.0 +--- + +# Get-DataverseMakerPortalUrl + +## SYNOPSIS +Generates a URL to open the Power Apps Maker Portal for the current environment. + +## SYNTAX + +``` +Get-DataverseMakerPortalUrl [-TableName ] [-Connection ] + [-ProgressAction ] [] +``` + +## DESCRIPTION + +This cmdlet generates a URL that opens the Power Apps Maker Portal for the Dataverse environment associated with the current connection. + +The Maker Portal is where you can: +- Build and configure Power Apps +- Manage solutions +- View and manage tables (entities) +- Create and manage flows +- Work with connections and data sources + +Optionally, you can specify a table name to open that specific table's detail page in the Maker Portal. + +## EXAMPLES + +### Example 1: Get URL for Maker Portal home page +```powershell +PS C:\> Get-DataverseMakerPortalUrl -Connection $c +``` + +Returns a URL to open the Maker Portal home page for the connected environment. + +### Example 2: Get URL for a specific table in Maker Portal +```powershell +PS C:\> Get-DataverseMakerPortalUrl -Connection $c -TableName "contact" +``` + +Returns a URL to open the contact table's detail page in the Maker Portal. + +### Example 3: Open table from pipeline +```powershell +PS C:\> Get-DataverseEntityMetadata -Connection $c -TableName "account" | Get-DataverseMakerPortalUrl -Connection $c +``` + +Gets the account table metadata and generates a URL to open it in the Maker Portal. + +### Example 4: Open Maker Portal directly in browser +```powershell +PS C:\> Start-Process (Get-DataverseMakerPortalUrl -Connection $c -TableName "contact") +``` + +Opens the contact table in the Maker Portal directly in the default web browser. + +## PARAMETERS + +### -Connection +DataverseConnection instance obtained from Get-DataverseConnection cmdlet, or string specifying Dataverse organization URL (e.g. http://server.com/MyOrg/). If not provided, uses the default connection set via Get-DataverseConnection -SetAsDefault. + +```yaml +Type: ServiceClient +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TableName +The logical name of the table to open in the maker portal (e.g., 'account', 'contact'). If not provided, opens the Maker Portal home page. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: EntityName, LogicalName + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String +## OUTPUTS + +### System.String +## NOTES + +The Maker Portal URL format is: +- Home page: https://make.powerapps.com/environments/{environmentId}/home +- Table page: https://make.powerapps.com/environments/{environmentId}/entities/entity/{tableName} + +## RELATED LINKS + +[Power Apps Maker Portal](https://make.powerapps.com) diff --git a/Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseRecordUrl.md b/Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseRecordUrl.md new file mode 100644 index 000000000..75d153c97 --- /dev/null +++ b/Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseRecordUrl.md @@ -0,0 +1,204 @@ +--- +external help file: Rnwood.Dataverse.Data.PowerShell.Cmdlets.dll-Help.xml +Module Name: Rnwood.Dataverse.Data.PowerShell +online version: +schema: 2.0.0 +--- + +# Get-DataverseRecordUrl + +## SYNOPSIS +Generates a URL to open a record in the Dataverse web interface. + +## SYNTAX + +### ByAppUniqueName (Default) +``` +Get-DataverseRecordUrl [-TableName] [[-Id] ] [-AppUniqueName ] [-FormId ] + [-Connection ] [-ProgressAction ] [] +``` + +### ByAppId +``` +Get-DataverseRecordUrl [-TableName] [[-Id] ] [-AppId ] [-FormId ] + [-Connection ] [-ProgressAction ] [] +``` + +## DESCRIPTION + +This cmdlet generates a URL that can be used to open a record in the Dataverse web interface. If an ID is provided, the URL will open that specific record. If no ID is provided, the URL will open a form to create a new record. + +The generated URL can be: +- Opened directly in a web browser +- Shared with users via email or other communication +- Embedded in custom applications or workflows +- Used for deep-linking into specific records or forms + +The cmdlet can optionally include parameters to open the record in a specific app (by app ID or unique name) or with a specific form. + +## EXAMPLES + +### Example 1: Get URL for a specific record +```powershell +PS C:\> Get-DataverseRecordUrl -Connection $c -TableName "contact" -Id "12345678-1234-1234-1234-123456789012" +``` + +Returns a URL to open the contact record with the specified ID. + +### Example 2: Get URL to create a new record +```powershell +PS C:\> Get-DataverseRecordUrl -Connection $c -TableName "account" +``` + +Returns a URL to open a form for creating a new account record. + +### Example 3: Get URL for a record in a specific app by unique name +```powershell +PS C:\> Get-DataverseRecordUrl -Connection $c -TableName "contact" -Id "12345678-1234-1234-1234-123456789012" -AppUniqueName "myapp_12345" +``` + +Returns a URL to open the contact record in the app with unique name "myapp_12345". The app ID is automatically looked up (including unpublished apps). + +### Example 4: Get URL for a record in a specific app by ID +```powershell +PS C:\> Get-DataverseRecordUrl -Connection $c -TableName "contact" -Id "12345678-1234-1234-1234-123456789012" -AppId "87654321-4321-4321-4321-210987654321" +``` + +Returns a URL to open the contact record in the specified model-driven app by app ID. + +### Example 5: Get URL with a specific form +```powershell +PS C:\> Get-DataverseRecordUrl -Connection $c -TableName "account" -FormId "abcdefgh-abcd-abcd-abcd-abcdefghijkl" +``` + +Returns a URL to create a new account using the specified form. + +### Example 6: Generate URLs from pipeline +```powershell +PS C:\> Get-DataverseRecord -Connection $c -TableName "contact" | Get-DataverseRecordUrl -Connection $c -TableName "contact" +``` + +Generates URLs for all contact records returned by Get-DataverseRecord. + +## PARAMETERS + +### -AppId +The App ID to open the record in a specific model-driven app. + +```yaml +Type: Guid +Parameter Sets: ByAppId +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -AppUniqueName +The unique name of the app to open the record in a specific model-driven app. The app ID will be looked up automatically (including unpublished apps). + +```yaml +Type: String +Parameter Sets: ByAppUniqueName +Aliases: UniqueName + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Connection +DataverseConnection instance obtained from Get-DataverseConnection cmdlet, or string specifying Dataverse organization URL (e.g. http://server.com/MyOrg/). If not provided, uses the default connection set via Get-DataverseConnection -SetAsDefault. + +```yaml +Type: ServiceClient +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -FormId +The Form ID to open a specific form for the record. + +```yaml +Type: Guid +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Id +The ID of the record. If not provided, generates a URL for creating a new record. + +```yaml +Type: Guid +Parameter Sets: (All) +Aliases: RecordId + +Required: False +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TableName +The logical name of the table (e.g., 'account', 'contact'). + +```yaml +Type: String +Parameter Sets: (All) +Aliases: EntityName, LogicalName + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String +### System.Nullable`1[[System.Guid, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] +## OUTPUTS + +### System.String +## NOTES + +When using AppUniqueName, the cmdlet will query the appmodule entity to find the app ID, including unpublished apps. + +## RELATED LINKS diff --git a/Rnwood.Dataverse.Data.PowerShell/docs/Rnwood.Dataverse.Data.PowerShell.md b/Rnwood.Dataverse.Data.PowerShell/docs/Rnwood.Dataverse.Data.PowerShell.md index d34c81b4d..11c136399 100644 --- a/Rnwood.Dataverse.Data.PowerShell/docs/Rnwood.Dataverse.Data.PowerShell.md +++ b/Rnwood.Dataverse.Data.PowerShell/docs/Rnwood.Dataverse.Data.PowerShell.md @@ -26,6 +26,9 @@ Unpacks a Dataverse solution file using the Power Apps CLI. ### [Export-DataverseSolution](Export-DataverseSolution.md) Exports a solution from Dataverse using an asynchronous job with progress reporting. +### [Get-DataverseAdminPortalUrl](Get-DataverseAdminPortalUrl.md) +Generates a URL to open the Power Platform Admin Center for the current environment. + ### [Get-DataverseAppModule](Get-DataverseAppModule.md) Retrieves app module (model-driven app) information from a Dataverse environment. @@ -93,6 +96,9 @@ Retrieves tab information from a Dataverse form. ### [Get-DataverseIconSetIcon](Get-DataverseIconSetIcon.md) Retrieves available icons from supported online icon sets. +### [Get-DataverseMakerPortalUrl](Get-DataverseMakerPortalUrl.md) +Generates a URL to open the Power Apps Maker Portal for the current environment. + ### [Get-DataverseMsAppComponent](Get-DataverseMsAppComponent.md) Retrieves components from a .msapp file. @@ -142,6 +148,9 @@ Retrieves all principals (users or teams) who have shared access to a specific r Reads a folder of JSON files written out by `Set-DataverseRecordFolder` and converts back into a stream of PS objects. Together these commands can be used to extract and import data to and from files, for instance for inclusion in source control, or build/deployment assets. +### [Get-DataverseRecordUrl](Get-DataverseRecordUrl.md) +Generates a URL to open a record in the Dataverse web interface. + ### [Get-DataverseRelationshipMetadata](Get-DataverseRelationshipMetadata.md) Retrieves relationship metadata from Dataverse. diff --git a/docs/core-concepts/url-generation.md b/docs/core-concepts/url-generation.md new file mode 100644 index 000000000..435d7097a --- /dev/null +++ b/docs/core-concepts/url-generation.md @@ -0,0 +1,208 @@ +# URL Generation + +Generate URLs for accessing Dataverse resources in web browsers. These cmdlets help you create deep links to records, navigate to the Power Apps Maker Portal, or open the Power Platform Admin Center. + +## Overview + +The URL generation cmdlets provide a convenient way to: +- Share links to specific records with team members +- Create bookmarks for frequently accessed resources +- Integrate Dataverse URLs into custom applications or workflows +- Quickly navigate to administrative interfaces + +## Available Cmdlets + +### Get-DataverseRecordUrl + +Generates URLs to open specific records or create new records in the Dataverse web interface. + +**Basic Usage:** + +```powershell +# Get URL for a specific contact record +$url = Get-DataverseRecordUrl -Connection $c -TableName "contact" -Id $contactId + +# Get URL to create a new account +$url = Get-DataverseRecordUrl -Connection $c -TableName "account" + +# Open record in a specific app by unique name +$url = Get-DataverseRecordUrl -Connection $c -TableName "contact" -Id $contactId -AppUniqueName "sales_app" + +# Open record in a specific app by ID +$url = Get-DataverseRecordUrl -Connection $c -TableName "contact" -Id $contactId -AppId $appId + +# Open record with a specific form +$url = Get-DataverseRecordUrl -Connection $c -TableName "account" -FormId $formId +``` + +**Key Features:** +- Supports both existing records (with ID) and new record creation (without ID) +- Can specify app context using either: + - **AppUniqueName**: Looks up the app ID automatically (including unpublished apps) + - **AppId**: Uses the app ID directly +- Optional form ID parameter to open specific forms +- Works with pipeline input for batch URL generation + +### Get-DataverseMakerPortalUrl + +Generates URLs to open the Power Apps Maker Portal for the current environment. + +**Basic Usage:** + +```powershell +# Get URL for Maker Portal home page +$url = Get-DataverseMakerPortalUrl -Connection $c + +# Get URL for a specific table in the Maker Portal +$url = Get-DataverseMakerPortalUrl -Connection $c -TableName "contact" + +# Open Maker Portal directly in browser +Start-Process (Get-DataverseMakerPortalUrl -Connection $c -TableName "account") +``` + +**Key Features:** +- Opens the Maker Portal home page by default +- Optional table context to open a specific table's detail page +- Automatically determines the environment ID from the connection + +### Get-DataverseAdminPortalUrl + +Generates URLs to open the Power Platform Admin Center for the current environment. + +**Basic Usage:** + +```powershell +# Get URL for Admin Center +$url = Get-DataverseAdminPortalUrl -Connection $c + +# Open Admin Center directly in browser +Start-Process (Get-DataverseAdminPortalUrl -Connection $c) +``` + +**Key Features:** +- Opens the environment hub page in the Admin Center +- Automatically determines the environment ID from the connection + +## Common Scenarios + +### Sharing Record Links + +Generate and share URLs for specific records: + +```powershell +# Get URLs for all high-priority cases +$cases = Get-DataverseRecord -Connection $c -TableName "incident" -FilterValues @{ prioritycode = 1 } +$caseUrls = $cases | ForEach-Object { + [PSCustomObject]@{ + Title = $_.title + CaseNumber = $_.ticketnumber + URL = Get-DataverseRecordUrl -Connection $c -TableName "incident" -Id $_.incidentid + } +} +$caseUrls | Export-Csv -Path "high-priority-cases.csv" -NoTypeInformation +``` + +### Opening Records in Specific Apps + +Open records in different app contexts: + +```powershell +# Open contact in Sales Hub app +$salesUrl = Get-DataverseRecordUrl -Connection $c -TableName "contact" -Id $contactId -AppUniqueName "SalesHub" + +# Open same contact in Customer Service Hub +$serviceUrl = Get-DataverseRecordUrl -Connection $c -TableName "contact" -Id $contactId -AppUniqueName "CustomerServiceHub" +``` + +### Bulk URL Generation + +Generate URLs for multiple records efficiently: + +```powershell +# Generate URLs for all active accounts +Get-DataverseRecord -Connection $c -TableName "account" -FilterValues @{ statecode = 0 } | + Select-Object name, accountid, + @{Name='URL'; Expression={ Get-DataverseRecordUrl -Connection $c -TableName "account" -Id $_.accountid }} | + Export-Csv -Path "active-accounts-with-urls.csv" -NoTypeInformation +``` + +### Navigation Helper Functions + +Create helper functions for quick navigation: + +```powershell +function Open-DataverseRecord { + param( + [Parameter(Mandatory)] + [string]$TableName, + [Parameter(Mandatory)] + [guid]$Id, + [string]$AppUniqueName + ) + + $url = if ($AppUniqueName) { + Get-DataverseRecordUrl -TableName $TableName -Id $Id -AppUniqueName $AppUniqueName + } else { + Get-DataverseRecordUrl -TableName $TableName -Id $Id + } + + Start-Process $url +} + +# Usage +Open-DataverseRecord -TableName "contact" -Id $contactId -AppUniqueName "sales_app" +``` + +### Admin Portal Quick Access + +Quickly access the admin portal for your environment: + +```powershell +# Create a shortcut function +function Open-AdminPortal { + Start-Process (Get-DataverseAdminPortalUrl) +} + +# Usage +Open-AdminPortal +``` + +## URL Format Reference + +### Record URLs +``` +https://{org}.crm.dynamics.com/main.aspx?etn={table}&id={recordid}&pagetype=entityrecord +https://{org}.crm.dynamics.com/main.aspx?etn={table}&id={recordid}&pagetype=entityrecord&appid={appid} +https://{org}.crm.dynamics.com/main.aspx?etn={table}&pagetype=entityrecord (new record) +``` + +### Maker Portal URLs +``` +https://make.powerapps.com/environments/{envid}/home +https://make.powerapps.com/environments/{envid}/entities/entity/{tablename} +``` + +### Admin Portal URLs +``` +https://admin.powerplatform.microsoft.com/environments/{envid}/hub +``` + +## Best Practices + +1. **Use AppUniqueName when possible**: It's more maintainable than app IDs and works with unpublished apps +2. **Cache generated URLs**: If you're generating many URLs for the same records, consider caching them +3. **Validate app existence**: When using AppUniqueName, the cmdlet will error if the app isn't found +4. **Consider permissions**: Generated URLs still require the user to have appropriate permissions +5. **Use default connections**: Set a default connection to avoid repeating the `-Connection` parameter + +## Related Cmdlets + +- [`Get-DataverseConnection`](../../../Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseConnection.md) — Create or retrieve connections +- [`Get-DataverseRecord`](../../../Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseRecord.md) — Query records +- [`Get-DataverseAppModule`](../../../Rnwood.Dataverse.Data.PowerShell/docs/Get-DataverseAppModule.md) — Query app modules + +## See Also + +- [Cmdlet Documentation](../../../Rnwood.Dataverse.Data.PowerShell/docs/) +- [Connection Management](connections.md) +- [App Module Management](app-module-management.md) diff --git a/tests/Get-DataverseAdminPortalUrl.Tests.ps1 b/tests/Get-DataverseAdminPortalUrl.Tests.ps1 new file mode 100644 index 000000000..f8f16226d --- /dev/null +++ b/tests/Get-DataverseAdminPortalUrl.Tests.ps1 @@ -0,0 +1,41 @@ +. $PSScriptRoot/Common.ps1 + +Describe 'Get-DataverseAdminPortalUrl' { + Context 'Admin Portal URL Generation' { + It "Generates URL for environment page" { + $connection = getMockConnection + + $url = Get-DataverseAdminPortalUrl -Connection $connection + + # Verify URL structure + $url | Should -Not -BeNullOrEmpty + $url | Should -BeLike "https://admin.powerplatform.microsoft.com/environments/*" + $url | Should -BeLike "*/hub" + } + + It "Includes environment ID from connection" { + $connection = getMockConnection + + # Get the organization ID (which is the environment ID) + $whoami = Get-DataverseWhoAmI -Connection $connection + $envId = $whoami.OrganizationId + + $url = Get-DataverseAdminPortalUrl -Connection $connection + + # Verify URL includes the environment ID + $url | Should -BeLike "*/environments/$envId/hub" + } + + It "Works with default connection" { + $connection = getMockConnection + Set-DataverseConnectionAsDefault -Connection $connection + + # Call without explicit connection + $url = Get-DataverseAdminPortalUrl + + # Verify URL generated + $url | Should -Not -BeNullOrEmpty + $url | Should -BeLike "https://admin.powerplatform.microsoft.com/*" + } + } +} diff --git a/tests/Get-DataverseMakerPortalUrl.Tests.ps1 b/tests/Get-DataverseMakerPortalUrl.Tests.ps1 new file mode 100644 index 000000000..b608d2d5b --- /dev/null +++ b/tests/Get-DataverseMakerPortalUrl.Tests.ps1 @@ -0,0 +1,62 @@ +. $PSScriptRoot/Common.ps1 + +Describe 'Get-DataverseMakerPortalUrl' { + Context 'Maker Portal URL Generation' { + It "Generates URL for maker portal home page by default" { + $connection = getMockConnection + + $url = Get-DataverseMakerPortalUrl -Connection $connection + + # Verify URL structure + $url | Should -Not -BeNullOrEmpty + $url | Should -BeLike "https://make.powerapps.com/environments/*" + $url | Should -BeLike "*/home" + } + + It "Generates URL for a specific table" { + $connection = getMockConnection + + $url = Get-DataverseMakerPortalUrl -Connection $connection -TableName "contact" + + # Verify URL points to the table + $url | Should -BeLike "*/entities/entity/contact" + } + + It "Includes environment ID from connection" { + $connection = getMockConnection + + # Get the organization ID (which is the environment ID) + $whoami = Get-DataverseWhoAmI -Connection $connection + $envId = $whoami.OrganizationId + + $url = Get-DataverseMakerPortalUrl -Connection $connection + + # Verify URL includes the environment ID + $url | Should -BeLike "*/environments/$envId/*" + } + + It "Works with default connection" { + $connection = getMockConnection + Set-DataverseConnectionAsDefault -Connection $connection + + # Call without explicit connection + $url = Get-DataverseMakerPortalUrl + + # Verify URL generated + $url | Should -Not -BeNullOrEmpty + $url | Should -BeLike "https://make.powerapps.com/*" + } + + It "Supports pipeline input for TableName" { + $connection = getMockConnection -Entities contact + + # Create a table metadata object with LogicalName property + $metadata = Get-DataverseEntityMetadata -Connection $connection -TableName "contact" + + $url = $metadata | Get-DataverseMakerPortalUrl -Connection $connection + + # Verify URL includes the table + $url | Should -BeLike "*/entities/entity/contact" + } + } +} diff --git a/tests/Get-DataverseRecordUrl.Tests.ps1 b/tests/Get-DataverseRecordUrl.Tests.ps1 new file mode 100644 index 000000000..3d08d8fea --- /dev/null +++ b/tests/Get-DataverseRecordUrl.Tests.ps1 @@ -0,0 +1,125 @@ +. $PSScriptRoot/Common.ps1 + +Describe 'Get-DataverseRecordUrl' { + Context 'URL Generation for Records' { + It "Generates URL for existing record with ID" { + $connection = getMockConnection + + # Generate URL for a specific contact record + $recordId = [Guid]::NewGuid() + $url = Get-DataverseRecordUrl -Connection $connection -TableName "contact" -Id $recordId + + # Verify URL structure + $url | Should -Not -BeNullOrEmpty + $url | Should -BeLike "*main.aspx*" + $url | Should -BeLike "*etn=contact*" + $url | Should -BeLike "*id=$recordId*" + $url | Should -BeLike "*pagetype=entityrecord*" + } + + It "Generates URL for creating new record without ID" { + $connection = getMockConnection + + # Generate URL for creating a new account record + $url = Get-DataverseRecordUrl -Connection $connection -TableName "account" + + # Verify URL structure (no ID parameter) + $url | Should -Not -BeNullOrEmpty + $url | Should -BeLike "*main.aspx*" + $url | Should -BeLike "*etn=account*" + $url | Should -Not -BeLike "*id=*" + $url | Should -BeLike "*pagetype=entityrecord*" + } + + It "Includes AppId parameter when provided" { + $connection = getMockConnection + + $recordId = [Guid]::NewGuid() + $appId = [Guid]::NewGuid() + $url = Get-DataverseRecordUrl -Connection $connection -TableName "contact" -Id $recordId -AppId $appId + + # Verify URL includes appid + $url | Should -BeLike "*appid=$appId*" + } + + It "Resolves AppUniqueName to AppId" { + $connection = getMockConnection + + # Create a mock app module + $appId = [Guid]::NewGuid() + $appModule = @{ + appmoduleid = $appId + uniquename = "testapp_123" + name = "Test App" + } | Set-DataverseRecord -Connection $connection -TableName appmodule -CreateOnly -PassThru + + $recordId = [Guid]::NewGuid() + $url = Get-DataverseRecordUrl -Connection $connection -TableName "contact" -Id $recordId -AppUniqueName "testapp_123" + + # Verify URL includes the resolved appid + $url | Should -BeLike "*appid=$appId*" + } + + It "Includes FormId parameter when provided" { + $connection = getMockConnection + + $recordId = [Guid]::NewGuid() + $formId = [Guid]::NewGuid() + $url = Get-DataverseRecordUrl -Connection $connection -TableName "contact" -Id $recordId -FormId $formId + + # Verify URL includes formid + $url | Should -BeLike "*formid=$formId*" + } + + It "Includes both AppId and FormId when provided" { + $connection = getMockConnection + + $recordId = [Guid]::NewGuid() + $appId = [Guid]::NewGuid() + $formId = [Guid]::NewGuid() + $url = Get-DataverseRecordUrl -Connection $connection -TableName "contact" -Id $recordId -AppId $appId -FormId $formId + + # Verify URL includes both parameters + $url | Should -BeLike "*appid=$appId*" + $url | Should -BeLike "*formid=$formId*" + } + + It "Works with pipeline input from Get-DataverseRecord" { + $connection = getMockConnection -Entities contact + + # Create test records + $contact1 = @{ firstname = "John"; lastname = "Doe" } | Set-DataverseRecord -Connection $connection -TableName contact -CreateOnly -PassThru + $contact2 = @{ firstname = "Jane"; lastname = "Smith" } | Set-DataverseRecord -Connection $connection -TableName contact -CreateOnly -PassThru + + # Generate URLs from pipeline + $urls = Get-DataverseRecord -Connection $connection -TableName contact | Get-DataverseRecordUrl -Connection $connection -TableName contact + + # Verify URLs generated for both records + $urls | Should -HaveCount 2 + $urls[0] | Should -BeLike "*id=$($contact1.Id)*" + $urls[1] | Should -BeLike "*id=$($contact2.Id)*" + } + + It "Uses connection's organization URL" { + $connection = getMockConnection + + $url = Get-DataverseRecordUrl -Connection $connection -TableName "contact" + + # Verify URL uses the connection's org URL + $url | Should -BeLike "https://*" + } + + It "Works with default connection" { + $connection = getMockConnection + Set-DataverseConnectionAsDefault -Connection $connection + + # Call without explicit connection + $recordId = [Guid]::NewGuid() + $url = Get-DataverseRecordUrl -TableName "contact" -Id $recordId + + # Verify URL generated + $url | Should -Not -BeNullOrEmpty + $url | Should -BeLike "*id=$recordId*" + } + } +}