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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 57 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Now you can extend the build process explicitly calling additional tasks during
<AssemblyName>Some.Solution</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TALXIS.SDK.BuildTargets.Dataverse.Tasks" Version="0.*" />
<PackageReference Include="TALXIS.DevKit.Build.Dataverse.Tasks" Version="0.*" />
<!-- Add other project references like plugins, PCFs and scripts here...
<ProjectReference Include="..\something_else\dependency.csproj" />
-->
Expand All @@ -103,6 +103,7 @@ Now you can extend the build process explicitly calling additional tasks during

<Target Name="BuildDataverseSolution" BeforeTargets="Build" Condition="Exists('$(ProjectDir)$(SolutionRootPath)\Other\Solution.xml')">
<CallTarget Targets="ValidateSolutionComponentSchema"/>
<CallTarget Targets="ValidateConnectionReferences"/>
<CallTarget Targets="GenerateVersionNumber"/>
<CallTarget Targets="ApplyVersionNumber"/>
</Target>
Expand All @@ -113,38 +114,75 @@ Now you can extend the build process explicitly calling additional tasks during

We are happy to collaborate with developers and contributors interested in enhancing Power Platform development processes. If you have feedback, suggestions, or would like to contribute, please feel free to submit issues or pull requests.

### Local building and debugging
### Local debugging

#### Package project
#### Set up a testing project

Run the following terminal command in the folder `MSBuildTasks`:
Execute the following commands in PowerShell in a new folder outside of this repo to set up a testing project.

```
dotnet pack --configuration Release
```
```powershell
# Initialize a VS solution file
dotnet new sln --name Test

#### Consuming project
# Instal Power Platform .NET templates
dotnet new install TALXIS.DevKit.Templates.Dataverse

Add `nuget.config` file to your Dataverse solution project folder:
# Create a Dataverse solution project
dotnet new pp-solution `
--output "src/Solutions.Test" `
--PublisherName "publisher" `
--PublisherPrefix "pub" `
--allow-scripts yes

```xml
$csprojFilePath = "src/Solutions.Test/Solutions.Test.cdsproj"
$appendContent = @"
<ItemGroup>
<PackageReference Include="TALXIS.DevKit.Build.Dataverse.Tasks" Version="0.*" />
</ItemGroup>
<PropertyGroup>
<SolutionRootPath>Declarations</SolutionRootPath>
</PropertyGroup>
<Target Name="BuildDataverseSolution" BeforeTargets="Build">
<CallTarget Targets="ValidateConnectionReferences"/>
</Target>
"@

# Read the existing .cdsproj content
$csprojContent = Get-Content $csprojFilePath

# Append the content inside the <Project> element
$csprojContent = $csprojContent -replace '(</Project>)', "$appendContent`n`$1"

# Write the updated content back to the .csproj file
Set-Content -Path $csprojFilePath -Value $csprojContent
```
#### Build and pack the NuGet package with targets
```powershell
# !!! Replace with actual talxis/tools-devkit-build repository path
$repoPath = "/Users/tomasprokop/Desktop/Repos/msbuild-devkit-connrefs/tools-devkit-build"

# Build and pack the NuGet package
dotnet pack "$repoPath/src/Dataverse/MSBuildTasks" --configuration Release
```
#### Configure NuGet to use the local version of the package
```powershell
# Add `nuget.config` file which will point to the locally built .nupkg
@"
<configuration>
<packageSources>
<!-- package source is additive -->
<add key="LocalBuildTasks" value="/{REPOSITORY PATH}/src/MSBuildTasks/bin/Release/" />
<add key="LocalBuildTasks" value="$repoPath/src/Dataverse/MSBuildTasks/bin/Release/" />
</packageSources>
</configuration>
"@ | Set-Content "nuget.config"
```

Clear all cached packages:

```
#### Debug the build targets locally
Use the following commands to when you want to test build with the local version of the NuGet package:
```powershell
# Clear all locally cached NuGet packages
dotnet nuget locals --clear all
```

Rebuild the project:

```
# Rebuild the consuming project
dotnet build --no-incremental --force
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(MicrosoftBuildTasksCoreVersion)" />
<!-- <PackageReference Include="Microsoft.PowerApps.CLI.Core.osx-x64" Version="*" /> -->
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />

<PackageReference Include="System.Text.Json" Version="5.0.2" />
<!-- TODO: Package Deployer embedding WIP, for now we call PAC CLI -->
<!-- <Reference Include="Microsoft.Xrm.Tooling.PackageDeployment.CrmPackageExtentionBase">
<HintPath>$(PkgMicrosoft_PowerApps_CLI)\tools\Microsoft.Xrm.Tooling.PackageDeployment.CrmPackageExtentionBase.dll</HintPath>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@
<file src="bin\release\**\TALXIS.*" target="build/" />
<file src="bin\release\**\System.IO.Packaging.dll" target="build/" />
<file src="bin\release\**\Newtonsoft.Json.Schema.dll" target="build/" />
<file src="bin\release\**\System.Text.Json.dll" target="build/" />
</files>
</package>
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
<UsingTask TaskName="ValidateXmlFiles" AssemblyFile="$(TasksAssembly)"/>
<UsingTask TaskName="ValidateJsonFiles" AssemblyFile="$(TasksAssembly)"/>
<UsingTask TaskName="RetrieveProjectReferences" AssemblyFile="$(TasksAssembly)"/>
<UsingTask TaskName="ValidateConnectionReferences" AssemblyFile="$(TasksAssembly)"/>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project>
<Target Name="ValidateConnectionReferences">
<PropertyGroup>
<ConnectionReferencesSolutionPath Condition="'$(ConnectionReferencesSolutionPath)' == ''">$(DataverseSolutionSourceFolderFullPath)</ConnectionReferencesSolutionPath>
</PropertyGroup>
<Message Text="Starting connection references validation ..." Importance="high" />
<ValidateConnectionReferences ConnectionReferencesSolutionPath="$(ConnectionReferencesSolutionPath)" WorkflowDefinitions="@(Flows)" WorkflowMetadataFiles="@(WorkflowMetadataFiles)"/>
<Message Text="Connection references validation finished!" Importance="high" />
</Target>
</Project>
132 changes: 132 additions & 0 deletions src/Dataverse/MSBuildTasks/Tasks/ValidateConnectionReferences.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Text.Json;

public class ValidateConnectionReferences : Task
{
[Required]
public string ConnectionReferencesSolutionPath { get; set; } = string.Empty;

[Required]
public ITaskItem[] WorkflowDefinitions { get; set; }

[Required]
public ITaskItem[] WorkflowMetadataFiles { get; set; }

public override bool Execute()
{
List<string> errorMessages;

bool validationResult = ValidateAllFlowConnectionReferences(ConnectionReferencesSolutionPath, out errorMessages);

if (!validationResult)
{
foreach (var error in errorMessages)
{
Log.LogError(error);
}

Log.LogError("Connection reference validation failed. See output for details.");
}

return validationResult;
}

private bool ValidateAllFlowConnectionReferences(string connectionReferencesPath, out List<string> errorMessages)
{
errorMessages = new List<string>();

var flowFiles = WorkflowDefinitions.Select(item => item.ItemSpec).ToList();

if (flowFiles.Count == 0)
{
Log.LogMessage(MessageImportance.Normal, "No Power Automate flows found.");
return true; // No flows to validate
}

// Load connection references from Customizations.xml
var customizationsPath = Path.Combine(connectionReferencesPath, "Other", "Customizations.xml");
if (!File.Exists(customizationsPath))
{
errorMessages.Add($"Error: Customizations.xml not found at: {customizationsPath}");
return false;
}

var definedConnectionReferences = ExtractDefinedConnectionReferences(customizationsPath);

// Validate connection references for each flow
bool allValid = true;
foreach (var flowFile in flowFiles)
{
if (!ValidateFlowConnectionReferences(flowFile, definedConnectionReferences, out var flowErrors))
{
allValid = false;
errorMessages.AddRange(flowErrors); // Accumulate errors for this flow
}
}

return allValid;
}

private HashSet<string> ExtractDefinedConnectionReferences(string customizationsPath)
{
var doc = XDocument.Load(customizationsPath);
return new HashSet<string>(
doc.Descendants("connectionreference")
.Select(cr => cr.Attribute("connectionreferencelogicalname")?.Value)
.Where(name => !string.IsNullOrEmpty(name))
);
}

private bool ValidateFlowConnectionReferences(string flowJsonPath, HashSet<string> definedConnectionReferences, out List<string> errorMessages)
{
errorMessages = new List<string>();

if (!File.Exists(flowJsonPath))
{
errorMessages.Add($"Error: JSON definition not found for flow: {flowJsonPath}");
return false;
}

var jsonContent = File.ReadAllText(flowJsonPath);
try
{
using (JsonDocument document = JsonDocument.Parse(jsonContent))
{
JsonElement root = document.RootElement;

if (root.TryGetProperty("properties", out JsonElement propertiesElement) &&
propertiesElement.TryGetProperty("connectionReferences", out JsonElement connectionReferencesElement) &&
connectionReferencesElement.ValueKind == JsonValueKind.Object)
{
bool allValid = true;
foreach (JsonProperty connectionReference in connectionReferencesElement.EnumerateObject())
{
if (connectionReference.Value.TryGetProperty("connection", out JsonElement connectionElement) &&
connectionElement.TryGetProperty("connectionReferenceLogicalName", out JsonElement logicalNameElement))
{
string logicalName = logicalNameElement.GetString();
if (string.IsNullOrEmpty(logicalName) || !definedConnectionReferences.Contains(logicalName))
{
errorMessages.Add($"Error: Invalid connection reference '{logicalName}' in flow {flowJsonPath}");
allValid = false;
}
}
}
return allValid;
}
}
Log.LogMessage(MessageImportance.High, $"No connection references found in flow: {flowJsonPath}");
return true;
}
catch (JsonException ex)
{
errorMessages.Add($"Error: Failed to parse JSON for flow {flowJsonPath}: {ex.Message}");
return false;
}
}
}