diff --git a/.github/agents/ue-customization-manager.agent.md b/.github/agents/ue-customization-manager.agent.md
index 2cadceb..24c672f 100644
--- a/.github/agents/ue-customization-manager.agent.md
+++ b/.github/agents/ue-customization-manager.agent.md
@@ -45,28 +45,40 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz
- **CRITICAL**: After invoking a script with `run_in_terminal`, remain absolutely silent. Do not send any messages, status updates, or comments about the script running or the terminal awaiting input.
- **IMPORTANT**: The scripts do NOT wait for user input after they start running. All input is gathered upfront via `vscode/askQuestions`. Never say the script is "waiting for input" because it is not - it runs autonomously after the initial parameter gathering.
+7. **Understand Hosted Validation**:
+ - Recognize that [`azure-pipelines.yml`](../../azure-pipelines.yml) contains a hosted security-validation lane that uses fake UE references to validate both `templates/` and `Examples/`.
+ - Use the YAML-hosted fake compile flow in [`shims/compile-with-shims.yml`](../../shims/compile-with-shims.yml) when reasoning about hosted compile-only validation. It generates the transient runner, shim props, shim projects, shim sources, sets `UNIFIED_ENGINEERING_REFERENCE_PATH`, and compiles selected directories against the fake UE surface.
+ - Treat `shims/` as compile-only stand-ins for hosted validation and static analysis, not runtime replacements for a real UE installation.
+ - Remember that `pmllib/` is generated local output for PML template builds and is intentionally ignored in git.
+ - When investigating hosted Coverity capture problems, verify that the Polaris-wrapped fake compile is using `-Rebuild` so compiler activity is emitted during capture.
+
## Key Script Understanding
### Setup-UECustomizationEnvironment.ps1
+
**Purpose**: Creates the initial customization environment structure and unpacks AVEVA binaries.
**When to use**:
+
- First time setting up a UE customization environment
- Need to reinitialize environment components
- Setting up on a new machine
- Reusing an existing environment without unpacking again
**Key parameters**:
+
- -ReuseEnvironment (optional): If specified, skips unpacking and reuses the existing configuration (script validates environment exists).
- CustomizationRootDir is automatically set to the parent of the scripts folder (non-configurable)
**Parameter Gathering Workflow**:
+
1. Use `vscode/askQuestions` to ask the user if they want to reuse an existing environment
2. If yes, pass -ReuseEnvironment to skip unpacking and reuse configuration
3. If no, perform full setup (script will validate and handle all checks)
4. **After invoking the script, remain completely silent** - do not send any messages about progress, terminal status, or awaiting input
**Important notes**:
+
- Requires UE command in PATH
- Sets environment variables: CAF_ADDINS_PATH, CAF_UIC_PATH at User level
- Creates subdirectories: addins/, samples/
@@ -77,76 +89,91 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz
- To create add-ins after setup, use New-UEAddin.ps1 in a separate step
**Example guidance**:
+
- "This script creates the foundation. Run it first to initialize your environment."
- "After setup completes, you can create add-ins using New-UEAddin.ps1"
- "If you want to skip unpacking and reuse your existing environment, I can pass -ReuseEnvironment."
### New-UEAddin.ps1
+
**Purpose**: Creates a new add-in configuration and source code by copying, renaming, and customizing a template folder.
**When to use**:
+
- After Setup-UECustomizationEnvironment.ps1 has been run
- Adding a new add-in to the environment
- Creating multiple add-ins in the same environment
**Key parameters**:
+
- -AddinName (mandatory): Name of the add-in to create. Use `vscode/askQuestions` to gather this if not provided by user.
- -Template (mandatory): Name of the template to use. Available templates are in the `templates/` folder. Use `vscode/askQuestions` to present available template options and gather the selection.
- -Module (mandatory): UE module to target. Valid values: Model, Engineering, EngineeringConfiguration, Isodraft, Draw, Paragon, Spooler, Monitor. Use `vscode/askQuestions` to present these as options and gather the selection.
- CustomizationRootDir is automatically set to the parent of the scripts folder (non-configurable)
**Parameter Gathering Workflow**:
+
1. List available templates from the `templates/` directory using: `Get-ChildItem -Path templates -Directory | Select-Object -ExpandProperty Name | Out-String` (the `| Out-String` is critical to ensure all templates are captured in terminal output)
2. Use `vscode/askQuestions` to gather AddinName, Module, and Template in a single call
3. Pass all parameters to the script - never let the script prompt interactively
**Validation**:
+
- -AddinName must contain only A-Za-z characters (no numbers, spaces, or special characters)
**Important notes**:
+
- Requires existing customization environment (will fail if Setup hasn't been run)
- Always gather template, module, and add-in name via `vscode/askQuestions` before calling the script
- Creates XML entries in the module-specific Addins and Customization files (e.g., Engineering → TagsAddins.xml and TagsCustomization.xml) when the selected template contains a `.uic` file
- **Copies templates/[TemplateName]/ to source/[AddinName]/** using robocopy
- Excludes build artifacts (.vs, bin, obj, logs, log directories)
-- Excludes compiler/cache files (*.user, *.suo, *.pdb, etc.)
+- Excludes compiler/cache files (_.user, _.suo, \*.pdb, etc.)
- Renames all files/folders/references containing the template name to the new add-in name
- Replaces all text occurrences of the template name in copied files with the new add-in name
- Can be called multiple times for different add-ins, with same or different templates
**Example guidance**:
+
- "I'll gather the required information for creating your add-in"
- "I need to know the add-in name, which template to use, and which UE module to target"
- "Each add-in needs a unique name that will be referenced in XML configuration files"
### Remove-UEAddin.ps1
+
**Purpose**: Removes an existing add-in configuration and source code from the customization environment.
**When to use**:
+
- Need to remove an add-in that's no longer needed
- Cleaning up specific add-ins while keeping others
- Removing configurations before environment cleanup
**⚠️ SAFETY REQUIREMENT**:
+
- **ALWAYS use `vscode/askQuestions` to confirm with the user before running this script**
- Ask: "Are you sure you want to remove the add-in '[AddinName]'? This will delete the source code folder and all configuration files."
- Only proceed if the user confirms with "yes" or similar affirmative response
- If the user declines, do not run the script
**Key parameters**:
+
- -AddinName (mandatory): Name of the add-in to remove. Use `vscode/askQuestions` to gather this if not provided.
- CustomizationRootDir is automatically set to the parent of the scripts folder (non-configurable)
**Parameter Gathering Workflow**:
+
1. Use `vscode/askQuestions` to gather AddinName
2. Use `vscode/askQuestions` to confirm the deletion with the user
3. Only proceed if user confirms
4. Pass parameters to the script - never let the script prompt interactively
**Validation**:
+
- -AddinName must contain only A-Za-z characters (no numbers, spaces, or special characters)
**Important notes**:
+
- Scans and removes XML entries across all module-specific `*Addins.xml` and `*Customization.xml` files under the `addins/` folder
- Deletes the corresponding `.uic` file and the add-in DLL (`addins/[AddinName].dll`) if present
- **Deletes the entire `source/[AddinName]/` folder and all its contents**
@@ -155,27 +182,33 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz
- Does not affect environment structure or other add-ins
**Example guidance**:
+
- "I'll gather the add-in name and confirmation before removing"
- "This operation is destructive and will delete the add-in configuration and source code"
### Clear-UECustomizationEnvironment.ps1
+
**Purpose**: Removes customization environment folders (samples and addins) while preserving custom add-in source code.
**When to use**:
+
- Cleaning up after development
- Removing unpacked samples and configuration files
- Starting fresh with a new environment without losing custom add-in projects
**Key parameters**:
+
- CustomizationRootDir is automatically set to the parent of the scripts folder (non-configurable)
**⚠️ SAFETY REQUIREMENT**:
+
- **ALWAYS use `vscode/askQuestions` to confirm with the user before running this script**
- Ask: "Are you sure you want to clear the customization environment? This will delete the samples/ and addins/ folders (but preserve your source/ code)."
- Only proceed if the user confirms with "yes" or similar affirmative response
- If the user declines, do not run the script
**Important notes**:
+
- **Deletes samples/ and addins/ folders** (unpacked binaries and configurations)
- **Preserves the source/ folder** with all custom add-in projects
- To remove individual add-ins, use Remove-UEAddin.ps1 instead
@@ -184,28 +217,34 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz
- Use this when you want to clean the environment but keep your custom add-in code
**Example guidance**:
+
- "This cleans up the unpacked files but keeps your custom add-in source code intact"
- "To remove specific add-ins, use Remove-UEAddin.ps1 first"
- Runs non-interactively without confirmation prompts
- Environment variables are NOT unset (use manual cleanup if needed)
**Example guidance**:
+
- "This removes the source, addins, and binaries folders but keeps your root directory intact"
- "Safe to run after you're done with an environment setup"
### Start-UE.ps1
+
**Purpose**: Launches AVEVA Unified Engineering with properly restored environment variables.
**When to use**:
+
- Starting UE for testing or development
- Ensuring environment variables are correctly set before launching
- After building add-ins and ready to test
**Key parameters**:
+
- Optional: `-Module`, `-Project`, `-Username`, `-Mdb` (forwarded to UE as `--module`, `--project`, `--username`, `--mdb`)
- CustomizationRootDir is automatically set to the parent of the scripts folder (non-configurable)
**Important notes**:
+
- Restores environment variables from `environment.json` using `Restore-EnvironmentVariables`
- Requires that `Setup-UECustomizationEnvironment.ps1` has been run previously
- If `environment.json` is not found, warns the user but attempts to launch UE anyway
@@ -214,85 +253,138 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz
- When asked to launch UE, prompt the user to confirm whether to include any of the optional UE arguments and pass those values into `Start-UE.ps1`.
**Example guidance**:
+
- "Use this script instead of running 'ue' directly to ensure your environment is properly configured"
- "After building your add-ins, run Start-UE.ps1 to test them in AVEVA Unified Engineering"
- "For security, I'll collect your password via PowerShell credential prompt instead of asking in chat"
- "The password will be cleared from memory immediately after launching UE"
+### Hosted Validation and Fake Compile
+
+**Purpose**: Help users understand and troubleshoot the hosted validation path that cannot depend on a real UE installation.
+
+**Key components**:
+
+- [`azure-pipelines.yml`](../../azure-pipelines.yml): Contains the hosted test lane and the optional hosted security-validation lane.
+- [`shims/compile-with-shims.yml`](../../shims/compile-with-shims.yml): Generates the transient fake compile runner plus `.tmp` shim props, sources, and project files from `shims/shim-projects.yml`, builds the compile-only shim assemblies, and compiles `templates/` and `Examples/` against them.
+- [`scripts/Build-AddIns.ps1`](../../scripts/Build-AddIns.ps1): Supports `-SkipPostBuildEvent` and `-Rebuild` for deterministic compile-only builds.
+- [`shims/`](../../shims): Contains compile-only UE API stand-ins used for hosted validation and Coverity capture, plus the YAML shim definition and generation templates used to produce the runtime shim manifest at build time.
+
+**Important notes**:
+
+- Hosted fake compile is for validation only. It proves the code compiles against the expected API surface for CI and Coverity capture, but does not replace development or testing with a real UE runtime.
+- The security lane uses explicit self-checkout paths in the multi-checkout job. Do not assume relative paths like `./scripts/...` will be correct in that job.
+- The Polaris-wrapped fake compile must force rebuild behavior; otherwise Coverity can report zero captured files because the build becomes an incremental no-op.
+- `pmllib/` is generated by PML template build steps and should be treated as ignored local output, not authored source.
+
+**Example guidance**:
+
+- "If hosted validation fails, I can inspect the fake-compile path and the multi-checkout pipeline job before assuming a UE runtime issue."
+- "If Coverity reports zero captured files, I will verify that the Polaris build path is using the generated fake compile runner with `-Rebuild`."
+- "If you see a local `pmllib/` folder after building a PML template, that is generated output rather than source to commit."
+
## Typical Workflows
### Build Projects
+
`powershell
+
# Build all sample projects
+
pwsh -NoProfile -File scripts/Build-AddIns.ps1 -Directory samples
# Build all source projects
+
pwsh -NoProfile -File scripts/Build-AddIns.ps1 -Directory source
# Build any custom path
+
pwsh -NoProfile -File scripts/Build-AddIns.ps1 -Directory C:\Path\To\Projects -Configuration Release
`
### Initial Setup with Add-ins
+
`powershell
+
# 1. Setup the environment
+
.\scripts\Setup-UECustomizationEnvironment.ps1
# 2. Create the first add-in
+
.\scripts\New-UEAddin.ps1 -AddinName "MyFirstAddin"
# 3. Create more add-ins as needed
+
.\scripts\New-UEAddin.ps1 -AddinName "MySecondAddin"
# 4. Build your add-ins
+
.\scripts\Build-AddIns.ps1 -Directory source
# 5. Launch UE to test
+
.\scripts\Start-UE.ps1
# 6. When done, clean up
+
.\scripts\Clear-UECustomizationEnvironment.ps1
`
### Multiple Add-in Development
+
`powershell
+
# 1. Setup environment
+
.\scripts\Setup-UECustomizationEnvironment.ps1
# 2. Create multiple add-ins over time
+
.\scripts\New-UEAddin.ps1 -AddinName "Addin1"
.\scripts\New-UEAddin.ps1 -AddinName "Addin2"
.\scripts\New-UEAddin.ps1 -AddinName "Addin3"
# 3. Build and test
+
.\scripts\Build-AddIns.ps1 -Directory source
.\scripts\Start-UE.ps1
# 4. Remove one that's no longer needed
+
.\scripts\Remove-UEAddin.ps1 -AddinName "Addin2"
# 5. Clean up when done
+
.\scripts\Clear-UECustomizationEnvironment.ps1
`
### Iterative Development
+
`powershell
+
# 1. Initial setup
+
.\scripts\Setup-UECustomizationEnvironment.ps1
# 2. Create add-in
+
.\scripts\New-UEAddin.ps1 -AddinName "MyAddin"
# 3. Build and test cycle
+
.\scripts\Build-AddIns.ps1 -Directory source
.\scripts\Start-UE.ps1
+
# Modify code, rebuild, relaunch as needed...
# 4. If you need to remove and recreate
+
.\scripts\Remove-UEAddin.ps1 -AddinName "MyAddin"
.\scripts\New-UEAddin.ps1 -AddinName "MyAddin" -Template "TemplateAddIn" -Module "Model"
# 5. Final cleanup
+
.\scripts\Clear-UECustomizationEnvironment.ps1
`
@@ -308,8 +400,8 @@ These variables are available in the current session immediately and in future P
## Important Considerations
### Directory Structure
-`
-CustomizationRootDir/
+
+`CustomizationRootDir/
source/ # Add-in source code directory
TemplateAddIn/ # Template (preserved by Clear script)
[AddinName]/ # Auto-generated copies with substitutions
@@ -319,10 +411,10 @@ CustomizationRootDir/
DesignCustomization.xml, TagsCustomization.xml, ConfigurationCustomization.xml, ...
[AddinName].uic
[AddinName].dll
- binaries/ # Supporting binaries from MSIX unpack
-`
+ binaries/ # Supporting binaries from MSIX unpack`
### File Operations
+
- All scripts run non-interactively without prompting
- Scripts validate paths and parameters before operations
- AddinName validation enforces A-Za-z characters only
@@ -330,6 +422,7 @@ CustomizationRootDir/
- Errors are thrown with clear messages about what went wrong
### Error Handling
+
- Scripts check for required files before operations
- Missing files in remove operations generate warnings, not errors
- Invalid paths or missing prerequisites generate clear error messages
@@ -348,7 +441,7 @@ CustomizationRootDir/
3. **Sequential Guidance**: Help users understand the order of operations. Setup must run before New-UEAddin.
-4. **Patience with Long-Running Operations**:
+4. **Patience with Long-Running Operations**:
- The MSIX unpack operation (during Setup-UECustomizationEnvironment.ps1) can take several minutes to complete
- Use appropriate timeouts (300000ms or 5 minutes minimum) when running setup scripts
- **The scripts do NOT require any user input after they start** - all parameters are gathered upfront via `vscode/askQuestions`
@@ -380,6 +473,7 @@ CustomizationRootDir/
## When to Ask Clarifying Questions
Ask for clarification when:
+
- User intent is ambiguous (is this first setup or modification?)
- Required parameters are missing
- Script prerequisites might not be met
@@ -390,9 +484,9 @@ Example: "Have you already run Setup-UECustomizationEnvironment.ps1? The New-UEA
## Available Documentation
The repository includes:
+
- **README.md**: Complete documentation of all scripts with detailed examples
- **Instructions.md**: Original instructions and setup guidance
- Each script is self-documented with inline comments
Always leverage the README documentation when helping users understand requirements or troubleshoot issues.
-
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e5c015e..a2bdd2e 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -2,50 +2,50 @@ name: Run Tests
on:
push:
- branches: [ main, develop ]
+ branches: [main]
pull_request:
- branches: [ main, develop ]
+ branches: [main]
jobs:
test:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v4
-
- - name: Set up PowerShell
- shell: pwsh
- run: |
- Write-Host "PowerShell version: $($PSVersionTable.PSVersion)"
-
- - name: Install NuGet provider
- shell: pwsh
- run: |
- if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) {
- Write-Host "Installing NuGet provider..."
- Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null
- }
-
- - name: Trust PSGallery
- shell: pwsh
- run: |
- $psGallery = Get-PSRepository -Name 'PSGallery' -ErrorAction SilentlyContinue
- if ($psGallery -and $psGallery.InstallationPolicy -ne 'Trusted') {
- Write-Host "Trusting PSGallery..."
- Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
- }
-
- - name: Install Pester
- shell: pwsh
- run: |
- if (-not (Get-Module -ListAvailable -Name Pester)) {
- Write-Host "Installing Pester module..."
- Install-Module Pester -Scope CurrentUser -Force -AllowClobber -SkipPublisherCheck | Out-Null
- }
-
- - name: Run tests
- shell: pwsh
- run: |
- cd ${{ github.workspace }}
- & .\scripts\Tests\Run-Tests.ps1 -Quick
- continue-on-error: false
+ - uses: actions/checkout@v4
+
+ - name: Set up PowerShell
+ shell: pwsh
+ run: |
+ Write-Host "PowerShell version: $($PSVersionTable.PSVersion)"
+
+ - name: Install NuGet provider
+ shell: pwsh
+ run: |
+ if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) {
+ Write-Host "Installing NuGet provider..."
+ Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null
+ }
+
+ - name: Trust PSGallery
+ shell: pwsh
+ run: |
+ $psGallery = Get-PSRepository -Name 'PSGallery' -ErrorAction SilentlyContinue
+ if ($psGallery -and $psGallery.InstallationPolicy -ne 'Trusted') {
+ Write-Host "Trusting PSGallery..."
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
+ }
+
+ - name: Install Pester
+ shell: pwsh
+ run: |
+ if (-not (Get-Module -ListAvailable -Name Pester)) {
+ Write-Host "Installing Pester module..."
+ Install-Module Pester -Scope CurrentUser -Force -AllowClobber -SkipPublisherCheck | Out-Null
+ }
+
+ - name: Run tests
+ shell: pwsh
+ run: |
+ cd ${{ github.workspace }}
+ & .\scripts\Tests\Run-Tests.ps1 -Quick
+ continue-on-error: false
diff --git a/.gitignore b/.gitignore
index 18a9596..8b4abf6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,6 +49,9 @@ bld/
# Build results on 'Bin' directories
**/[Bb]in/*
+# Local UE customization runtime output
+pmllib/
+shims/generated/
# Uncomment if you have tasks that rely on *.refresh files to move binaries
# (https://github.com/github/gitignore/pull/3736)
#!**/[Bb]in/*.refresh
diff --git a/README.md b/README.md
index 604efbd..e76eb9b 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,7 @@ The UE Customization Manager agent:
- **Explains workflows** - Helps you understand the proper sequence of operations
- **Validates operations** - Warns about destructive operations and validates parameters
- **Troubleshoots issues** - Provides context-aware assistance based on README documentation
+- **Explains hosted validation** - Understands the fake-compile, shim, and hosted security-validation path used in CI
- **Launches UE** - Can start AVEVA Unified Engineering when requested
- **Manages the lifecycle** - From initial setup through add-in creation to final cleanup
@@ -111,6 +112,12 @@ You can interact with the agent using natural language in the GitHub Copilot cha
@ue-customization-manager The script says the environment doesn't exist
@ue-customization-manager What files will be deleted if I run Clear?
+
+@ue-customization-manager Why did hosted security validation fail?
+
+@ue-customization-manager How does fake compile work for templates and Examples?
+
+@ue-customization-manager Why did a local pmllib folder appear after building a PML template?
```
### Benefits of Using the Agent
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 0000000..ef2bc7b
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,194 @@
+# Hybrid validation pipeline for the GitHub-hosted UE customization repo.
+#
+# This pipeline is intended to be connected to GitHub through the Azure Pipelines GitHub App
+# so its checks surface back into GitHub pull requests targeting main.
+#
+# The hosted test stage can run immediately on the Dabacon Products stateful hosted pools.
+# The security stage remains opt-in, but now uses the hosted-compatible fake-compile flow
+# on the Dabacon Products stateful hosted Ubuntu pool instead of generic vmImage selection.
+# Templates is consumed from Dabacon Products/Templates.
+# The Dabacon Products project still needs:
+# - a self-hosted Windows pool with UE installed and MSBuild available
+# - service connections named `Coverity on Polaris` and `BlackDuckScanner`
+
+resources:
+ repositories:
+ - repository: Templates
+ type: git
+ name: Dabacon Products/Templates
+ ref: refs/heads/feature/uecf-security-template-offload
+ - repository: antiMalwareTemplate
+ type: git
+ name: Architecture/Architecture
+ ref: refs/heads/antiMalwareVersions/latest
+
+variables:
+ - template: variables.yml@Templates
+ - name: CoverityProjectTeam
+ value: .RnD.Polaris.Dabacon-Products.SecurityAdvisors
+ - name: BlackDuckProjectName
+ value: AVEVA.UnifiedEngineeringCustomizationFramework
+
+trigger:
+ batch: true
+ branches:
+ include:
+ - main
+
+pr:
+ autoCancel: true
+ branches:
+ include:
+ - main
+
+stages:
+ - stage: Initialise
+ displayName: Initialise
+ jobs:
+ - template: jobs/publish-source.yml@Templates
+ parameters:
+ SourceFetchDepth: 1
+
+ - template: jobs/publish-templates.yml@Templates
+
+
+ - stage: BuildWithShims
+ displayName: Build With Shims
+ dependsOn: Initialise
+ jobs:
+ - job: BuildWithShims
+ displayName: Build With Shims
+ timeoutInMinutes: 120
+ pool:
+ name: Dabacon-Products-Microsoft-Hosted-Ubuntu
+ demands:
+ - ImageOverride -equals ubuntu-latest
+ - WorkFolder -equals /mnt/storage/sdc/storage
+ container: mcr.microsoft.com/dotnet/sdk:9.0
+ workspace:
+ clean: all
+ steps:
+ - template: steps/retrieve-source.yml@Templates
+ parameters:
+ IsIPR: false
+ ArtifactName: Source
+ DownloadPath: $(Pipeline.Workspace)/Source
+
+ - download: current
+ artifact: Templates
+ displayName: Download Templates artifact
+ patterns: "**"
+
+ - task: UseDotNet@2
+ displayName: Ensure .NET 9 SDK for shim build
+ retryCountOnTaskFailure: 3
+ inputs:
+ packageType: sdk
+ version: 9.0.x
+
+ - template: shims/shim-projects.yml
+ parameters:
+ OutputPath: $(Pipeline.Workspace)/Source/.tmp/generated-shim-manifest.json
+
+ - template: shims/compile-with-shims.yml
+ parameters:
+ Directory: templates,Examples
+ Configuration: Release
+ ShimManifestPath: $(Pipeline.Workspace)/Source/.tmp/generated-shim-manifest.json
+ GeneratedRunnerPath: $(Pipeline.Workspace)/Source/.tmp/generated-fake-ue-compile.ps1
+ GeneratedShimProjectsPath: $(Pipeline.Workspace)/Source/.tmp/generated-shims
+ GeneratedShimSourcesPath: $(Pipeline.Workspace)/Source/.tmp/generated-shim-sources
+ GeneratedShimPropsPath: $(Pipeline.Workspace)/Source/.tmp/generated-shim-props/Directory.Build.props
+ ShimOutputPath: $(Pipeline.Workspace)/Source/shims/artifacts/Release
+
+ - publish: $(Pipeline.Workspace)/Source/shims/artifacts/Release
+ artifact: Build.Release
+ displayName: Publish build artifact for component template comparison
+
+ - stage: HostedTests
+ displayName: Hosted Tests
+ dependsOn: Initialise
+ jobs:
+ - job: Pester
+ displayName: Pester
+ pool:
+ name: Dabacon-Products-Microsoft-Hosted-Ubuntu
+ demands:
+ - ImageOverride -equals ubuntu-latest
+ - WorkFolder -equals /mnt/storage/sdc/storage
+ container: mcr.microsoft.com/dotnet/sdk:9.0
+ workspace:
+ clean: all
+ steps:
+ - template: steps/retrieve-source.yml@Templates
+ parameters:
+ IsIPR: false
+ ArtifactName: Source
+ DownloadPath: $(Pipeline.Workspace)/Source
+
+ - pwsh: |
+ Write-Host "PowerShell version: $($PSVersionTable.PSVersion)"
+ displayName: Show PowerShell version
+
+ - pwsh: |
+ if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) {
+ Write-Host "Installing NuGet provider..."
+ Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null
+ }
+ displayName: Install NuGet provider
+
+ - pwsh: |
+ $psGallery = Get-PSRepository -Name 'PSGallery' -ErrorAction SilentlyContinue
+ if ($psGallery -and $psGallery.InstallationPolicy -ne 'Trusted') {
+ Write-Host "Trusting PSGallery..."
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
+ }
+ displayName: Trust PSGallery
+
+ - pwsh: |
+ if (-not (Get-Module -ListAvailable -Name Pester)) {
+ Write-Host "Installing Pester module..."
+ Install-Module Pester -Scope CurrentUser -Force -AllowClobber -SkipPublisherCheck | Out-Null
+ }
+ displayName: Install Pester
+
+ - pwsh: |
+ ./scripts/Tests/Run-Tests.ps1 -Quick
+ displayName: Run tests
+ workingDirectory: $(Pipeline.Workspace)/Source
+
+ - template: uecf/component-coverity-stage.yml@Templates
+ parameters:
+ CoverityTeamName: $(CoverityProjectTeam)
+ ProjectName: $(BlackDuckProjectName)
+
+ - stage: AntiMalware
+ displayName: AntiMalware
+ dependsOn:
+ - Initialise
+ - BuildWithShims
+ jobs:
+ - template: jobs/antimalware.yml@Templates
+ parameters:
+ JobName: AntiMalwareScan
+
+ - stage: CodeAnalysisLogs
+ displayName: CodeAnalysisLogs
+ dependsOn: AntiMalware
+ jobs:
+ - template: jobs/publish-CodeAnalysisLogs.yml@Templates
+ parameters:
+ Analysers:
+ []
+
+ - stage: BlackDuck
+ displayName: BlackDuck
+ dependsOn: BuildWithShims
+ jobs:
+ - template: component-blackduck.yml@Templates
+ parameters:
+ BuildArtifact: Build.Release
+ BlackDuckGroup: DabaconGroup
+ IsIPR: false
+ ProjectName: $(BlackDuckProjectName)
+ PackageVersion: $(Build.SourceBranchName)-$(Build.BuildId)
diff --git a/scripts/Build-AddIns.ps1 b/scripts/Build-AddIns.ps1
index 932ddde..a329f78 100644
--- a/scripts/Build-AddIns.ps1
+++ b/scripts/Build-AddIns.ps1
@@ -1,7 +1,9 @@
param(
[Parameter(Mandatory = $false)]
[string]$Directory = "source",
- [string]$Configuration = "Debug"
+ [string]$Configuration = "Debug",
+ [switch]$SkipPostBuildEvent,
+ [switch]$Rebuild
)
$ErrorActionPreference = "Stop"
@@ -33,7 +35,33 @@ if (-not $projects) {
$failed = @()
foreach ($proj in $projects) {
Write-Host "--- Building $($proj.FullName) ---" -ForegroundColor Cyan
- dotnet build $proj.FullName -c $Configuration -clp:Summary -nologo
+ $buildArgs = @(
+ 'build'
+ $proj.FullName
+ '-c'
+ $Configuration
+ '-clp:Summary'
+ '-nologo'
+ )
+
+ if ($env:FrameworkPathOverride) {
+ $buildArgs += "-p:FrameworkPathOverride=$($env:FrameworkPathOverride)"
+ }
+
+ if ($env:TargetFrameworkRootPath) {
+ $buildArgs += "-p:TargetFrameworkRootPath=$($env:TargetFrameworkRootPath)"
+ }
+
+ if ($Rebuild) {
+ $buildArgs += '-t:Rebuild'
+ }
+
+ if ($SkipPostBuildEvent) {
+ $buildArgs += '-p:RunPostBuildEvent=Never'
+ $buildArgs += '-p:PostBuildEvent='
+ }
+
+ dotnet @buildArgs
if ($LASTEXITCODE -ne 0) {
$failed += $proj.FullName
}
diff --git a/scripts/Tests/UECustomizationFunctions.Tests.ps1 b/scripts/Tests/UECustomizationFunctions.Tests.ps1
index 64fcef1..bb8a9d8 100644
--- a/scripts/Tests/UECustomizationFunctions.Tests.ps1
+++ b/scripts/Tests/UECustomizationFunctions.Tests.ps1
@@ -5,6 +5,8 @@ Import-Module -Name $modulePath -Force -ErrorAction Stop
Describe 'UECustomizationFunctions Module' {
BeforeAll {
+ $script:TempRoot = [System.IO.Path]::GetTempPath()
+
Mock -CommandName Set-EnvironmentVariableUserAndSession -ModuleName UECustomizationFunctions -MockWith {
Set-Item -Path ("Env:{0}" -f $Name) -Value $Value
}
@@ -45,7 +47,7 @@ Describe 'UECustomizationFunctions Module' {
Context 'Find-RequiredFile' {
It 'throws if file not found' {
- $temp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $temp = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
{ Find-RequiredFile -RootPath $temp.FullName -FileName 'nope.txt' } | Should -Throw
}
@@ -55,7 +57,7 @@ Describe 'UECustomizationFunctions Module' {
}
It 'returns full path when file exists' {
- $temp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $temp = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
$file = Join-Path $temp.FullName 'found.txt'
Set-Content -Path $file -Value 'test' -Encoding UTF8
@@ -68,7 +70,7 @@ Describe 'UECustomizationFunctions Module' {
}
It 'finds files in subdirectories' {
- $temp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $temp = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
$subdir = New-Item -Path (Join-Path $temp.FullName "subdir") -ItemType Directory
$file = Join-Path $subdir.FullName 'nested.txt'
@@ -84,7 +86,7 @@ Describe 'UECustomizationFunctions Module' {
Context 'Initialize-DirectoryStructure' {
It 'creates addins and pmllib directories' {
- $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
$addins = Join-Path $tempRoot.FullName 'addins'
$pmllib = Join-Path $tempRoot.FullName 'pmllib'
@@ -98,7 +100,7 @@ Describe 'UECustomizationFunctions Module' {
}
It 'creates nested directory paths' {
- $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
$addins = Join-Path $tempRoot.FullName 'nested\path\addins'
$pmllib = Join-Path $tempRoot.FullName 'nested\path\pmllib'
@@ -114,7 +116,7 @@ Describe 'UECustomizationFunctions Module' {
Context 'Set-EnvironmentConfiguration' {
It 'creates configuration file with correct content' {
- $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
$configPath = Join-Path $tempRoot.FullName 'environment.json'
$addins = Join-Path $tempRoot.FullName 'addins'
@@ -134,7 +136,7 @@ Describe 'UECustomizationFunctions Module' {
}
It 'sets environment variables in session' {
- $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
$configPath = Join-Path $tempRoot.FullName 'environment.json'
$addins = Join-Path $tempRoot.FullName 'addins'
@@ -159,7 +161,7 @@ Describe 'UECustomizationFunctions Module' {
}
It 'restores environment variables from config file' {
- $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
$configPath = Join-Path $tempRoot.FullName 'environment.json'
$addins = Join-Path $tempRoot.FullName 'addins'
@@ -193,7 +195,7 @@ Describe 'UECustomizationFunctions Module' {
}
It 'throws if destination is invalid' {
- $temp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $temp = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
$zipPath = Join-Path $temp.FullName 'test.zip'
Set-Content -Path $zipPath -Value 'dummy'
@@ -209,10 +211,10 @@ Describe 'UECustomizationFunctions Module' {
Context 'Install-UEFiles' {
It 'does not throw if source directory has no config files' {
- $temp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $temp = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
# Should warn but not throw when no config files are found
- { Install-UEFiles -UnpackPath $temp.FullName -AddinsFolder (Join-Path $env:TEMP 'addins') } | Should -Not -Throw
+ { Install-UEFiles -UnpackPath $temp.FullName -AddinsFolder (Join-Path $script:TempRoot 'addins') } | Should -Not -Throw
}
finally {
Remove-Item -Path $temp.FullName -Recurse -Force
@@ -228,7 +230,7 @@ Describe 'UECustomizationFunctions Module' {
Context 'Clear-UECustomizationEnvironment.ps1 Script' {
It 'removes environment.json during cleanup' {
- $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
# Create a mock environment.json
$configPath = Join-Path $tempRoot.FullName 'environment.json'
@@ -267,7 +269,7 @@ Describe 'UECustomizationFunctions Module' {
}
It 'updates DesignCustomization.xml only when template contains .uic' {
- $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid()))
+ $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid()))
try {
# Arrange environment structure
$addins = Join-Path $tempRoot.FullName 'addins'
diff --git a/scripts/UECustomizationFunctions.psm1 b/scripts/UECustomizationFunctions.psm1
index 3c5c361..5c8ae82 100644
--- a/scripts/UECustomizationFunctions.psm1
+++ b/scripts/UECustomizationFunctions.psm1
@@ -808,20 +808,38 @@ function Invoke-EnvironmentCleanup {
$excludeDirs = @('bin', 'obj', '.vs', 'logs', 'log')
$excludeFiles = @('*.user', '*.suo', '*.rsuser', '*.userprefs', '*.pdb', '*.ipdb', '*.iobj', '*.obj', '*.pch', '*.log', '*.tlog', '*.tmp', '*.cache', '*.ilk')
- $xdArgs = $excludeDirs | ForEach-Object { '/XD', $_ }
- $xfArgs = $excludeFiles | ForEach-Object { '/XF', $_ }
- $robocopyArgs = @($sourceTemplateDir, $targetAddinDir, '/E', '/NFL', '/NDL', '/NJH', '/NJS', '/NC', '/NS', '/R:2', '/W:1') + $xdArgs + $xfArgs
- & robocopy @robocopyArgs | Out-Null
- $rc = $LASTEXITCODE
- if ($rc -ge 8) {
- $exception = [System.InvalidOperationException]::new("Robocopy failed with exit code $rc while copying template.")
- $errorRecord = [System.Management.Automation.ErrorRecord]::new(
- $exception,
- 'RobocopyFailed',
- [System.Management.Automation.ErrorCategory]::InvalidOperation,
- $rc
- )
- $PSCmdlet.ThrowTerminatingError($errorRecord)
+ $robocopyCommand = Get-Command -Name 'robocopy' -ErrorAction SilentlyContinue
+ if ($robocopyCommand) {
+ $xdArgs = $excludeDirs | ForEach-Object { '/XD', $_ }
+ $xfArgs = $excludeFiles | ForEach-Object { '/XF', $_ }
+ $robocopyArgs = @($sourceTemplateDir, $targetAddinDir, '/E', '/NFL', '/NDL', '/NJH', '/NJS', '/NC', '/NS', '/R:2', '/W:1') + $xdArgs + $xfArgs
+ & $robocopyCommand.Source @robocopyArgs | Out-Null
+ $rc = $LASTEXITCODE
+ if ($rc -ge 8) {
+ $exception = [System.InvalidOperationException]::new("Robocopy failed with exit code $rc while copying template.")
+ $errorRecord = [System.Management.Automation.ErrorRecord]::new(
+ $exception,
+ 'RobocopyFailed',
+ [System.Management.Automation.ErrorCategory]::InvalidOperation,
+ $rc
+ )
+ $PSCmdlet.ThrowTerminatingError($errorRecord)
+ }
+ }
+ else {
+ Copy-Item -Path $sourceTemplateDir -Destination $targetAddinDir -Recurse -Force
+
+ foreach ($excludedDir in $excludeDirs) {
+ Get-ChildItem -Path $targetAddinDir -Directory -Recurse -Force -ErrorAction SilentlyContinue |
+ Where-Object { $_.Name -eq $excludedDir } |
+ Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
+ }
+
+ foreach ($excludedPattern in $excludeFiles) {
+ Get-ChildItem -Path $targetAddinDir -File -Recurse -Force -ErrorAction SilentlyContinue |
+ Where-Object { $_.Name -like $excludedPattern } |
+ Remove-Item -Force -ErrorAction SilentlyContinue
+ }
}
$itemsToRename = Get-ChildItem -Path $targetAddinDir -Recurse -Force | Where-Object { $_.Name -like "*$Template*" }
diff --git a/shims/compile-with-shims.yml b/shims/compile-with-shims.yml
new file mode 100644
index 0000000..d3ff76a
--- /dev/null
+++ b/shims/compile-with-shims.yml
@@ -0,0 +1,452 @@
+parameters:
+ - name: Directory
+ type: string
+ default: templates,Examples
+ - name: Configuration
+ type: string
+ default: Release
+ - name: ShimManifestPath
+ type: string
+ default: $(Pipeline.Workspace)/Source/.tmp/generated-shim-manifest.json
+ - name: GeneratedRunnerPath
+ type: string
+ default: $(Pipeline.Workspace)/Source/.tmp/generated-fake-ue-compile.ps1
+ - name: GeneratedShimProjectsPath
+ type: string
+ default: $(Pipeline.Workspace)/Source/.tmp/generated-shims
+ - name: GeneratedShimSourcesPath
+ type: string
+ default: $(Pipeline.Workspace)/Source/.tmp/generated-shim-sources
+ - name: GeneratedShimPropsPath
+ type: string
+ default: $(Pipeline.Workspace)/Source/.tmp/generated-shim-props/Directory.Build.props
+ - name: ShimOutputPath
+ type: string
+ default: $(Pipeline.Workspace)/Source/shims/artifacts/Release
+ - name: Rebuild
+ type: boolean
+ default: false
+
+steps:
+ - pwsh: |
+ $generatedRunnerPath = '${{ parameters.GeneratedRunnerPath }}'
+ $generatedRunnerDirectory = Split-Path -Path $generatedRunnerPath -Parent
+ if (-not [string]::IsNullOrWhiteSpace($generatedRunnerDirectory)) {
+ $null = New-Item -ItemType Directory -Path $generatedRunnerDirectory -Force
+ }
+
+ $runnerScript = @'
+ #Requires -Version 7.0
+
+ [CmdletBinding()]
+ param(
+ [string[]]$Directory = @('Examples'),
+ [string]$Configuration = 'Release',
+ [string]$ShimProjectsPath,
+ [string]$ShimManifestPath,
+ [string]$GeneratedShimProjectsPath,
+ [string]$GeneratedShimSourcesPath,
+ [string]$GeneratedShimPropsPath,
+ [string]$ShimOutputPath,
+ [switch]$Rebuild
+ )
+
+ $ErrorActionPreference = 'Stop'
+
+ $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
+ $defaultGeneratedShimManifestPath = Join-Path $repoRoot '.tmp\generated-shim-manifest.json'
+ $defaultGeneratedShimProjectsPath = Join-Path $repoRoot '.tmp\generated-shims'
+ $defaultGeneratedShimSourcesPath = Join-Path $repoRoot '.tmp\generated-shim-sources'
+ $defaultGeneratedShimPropsPath = Join-Path $repoRoot '.tmp\generated-shim-props\Directory.Build.props'
+
+ if (-not $ShimProjectsPath) {
+ $ShimProjectsPath = Join-Path $repoRoot 'shims'
+ }
+
+ if (-not $ShimManifestPath) {
+ if (Test-Path -LiteralPath $defaultGeneratedShimManifestPath) {
+ $ShimManifestPath = $defaultGeneratedShimManifestPath
+ }
+ else {
+ throw (
+ 'Shim manifest path was not provided and no generated manifest was found. ' +
+ 'Generate .tmp/generated-shim-manifest.json from shims/shim-projects.yml ' +
+ 'or pass -ShimManifestPath explicitly.'
+ )
+ }
+ }
+
+ if (-not $GeneratedShimProjectsPath) {
+ $GeneratedShimProjectsPath = $defaultGeneratedShimProjectsPath
+ }
+
+ if (-not $GeneratedShimSourcesPath) {
+ $GeneratedShimSourcesPath = $defaultGeneratedShimSourcesPath
+ }
+
+ if (-not $GeneratedShimPropsPath) {
+ $GeneratedShimPropsPath = $defaultGeneratedShimPropsPath
+ }
+
+ if (-not $ShimOutputPath) {
+ $ShimOutputPath = Join-Path $ShimProjectsPath "artifacts\$Configuration"
+ }
+
+ $resolvedShimProjectsPath = (Resolve-Path $ShimProjectsPath).Path
+ $resolvedShimManifestPath = (Resolve-Path $ShimManifestPath).Path
+ $resolvedGeneratedShimProjectsPath = [System.IO.Path]::GetFullPath($GeneratedShimProjectsPath)
+ $resolvedGeneratedShimSourcesPath = [System.IO.Path]::GetFullPath($GeneratedShimSourcesPath)
+ $resolvedGeneratedShimPropsPath = [System.IO.Path]::GetFullPath($GeneratedShimPropsPath)
+ $resolvedShimOutputDirectory = [System.IO.Path]::GetFullPath($ShimOutputPath)
+ $resolvedShimPropsDirectory = Split-Path -Path $resolvedGeneratedShimPropsPath -Parent
+ $buildScriptPath = Join-Path $repoRoot 'scripts\Build-AddIns.ps1'
+
+ function Ensure-NetFrameworkReferenceAssemblies {
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$WorkspaceRoot
+ )
+
+ $packageId = 'Microsoft.NETFramework.ReferenceAssemblies.net481'
+ $packageVersion = '1.0.3'
+ $packageRoot = Join-Path $WorkspaceRoot '.tmp\netfx-reference-assemblies'
+ $packageDir = Join-Path $packageRoot "$packageId.$packageVersion"
+ $packageArchive = Join-Path $packageRoot "$packageId.$packageVersion.nupkg"
+ $referenceDir = Join-Path $packageDir 'build\.NETFramework\v4.8.1'
+ $frameworkRoot = Join-Path $packageDir 'build\'
+
+ if (-not (Test-Path -LiteralPath $referenceDir)) {
+ $null = New-Item -ItemType Directory -Path $packageRoot -Force
+
+ $packageUrl = "https://www.nuget.org/api/v2/package/$packageId/$packageVersion"
+ Write-Host ("Downloading .NET Framework reference assemblies from {0}" -f $packageUrl) -ForegroundColor Cyan
+ Invoke-WebRequest -Uri $packageUrl -OutFile $packageArchive
+
+ if (Test-Path -LiteralPath $packageDir) {
+ Remove-Item -LiteralPath $packageDir -Recurse -Force
+ }
+
+ Expand-Archive -Path $packageArchive -DestinationPath $packageDir -Force
+ }
+
+ if (-not (Test-Path -LiteralPath $referenceDir)) {
+ throw "Reference assembly directory not found after package extraction: $referenceDir"
+ }
+
+ return [PSCustomObject]@{
+ FrameworkPathOverride = $referenceDir
+ TargetFrameworkRootPath = $frameworkRoot
+ }
+ }
+
+ function Import-ShimManifest {
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$Path
+ )
+
+ $extension = [System.IO.Path]::GetExtension($Path)
+ switch ($extension.ToLowerInvariant()) {
+ '.json' {
+ return Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json -AsHashtable
+ }
+ '.psd1' {
+ return Import-PowerShellDataFile -Path $Path
+ }
+ default {
+ throw "Unsupported shim manifest format '$extension'. Supported formats are .json and .psd1."
+ }
+ }
+ }
+
+ $shimManifest = Import-ShimManifest -Path $resolvedShimManifestPath
+ $shimProjects = if ($shimManifest.ContainsKey('Projects')) {
+ $shimManifest.Projects
+ }
+ else {
+ $null
+ }
+
+ if (-not $shimProjects -or $shimProjects.Count -eq 0) {
+ throw "No shim projects were defined in $resolvedShimManifestPath"
+ }
+
+ if (Test-Path -LiteralPath $resolvedGeneratedShimProjectsPath) {
+ Remove-Item -LiteralPath $resolvedGeneratedShimProjectsPath -Recurse -Force
+ }
+
+ if (Test-Path -LiteralPath $resolvedGeneratedShimSourcesPath) {
+ Remove-Item -LiteralPath $resolvedGeneratedShimSourcesPath -Recurse -Force
+ }
+
+ if (Test-Path -LiteralPath $resolvedShimPropsDirectory) {
+ Remove-Item -LiteralPath $resolvedShimPropsDirectory -Recurse -Force
+ }
+
+ $null = New-Item -ItemType Directory -Path $resolvedGeneratedShimProjectsPath -Force
+ $null = New-Item -ItemType Directory -Path $resolvedGeneratedShimSourcesPath -Force
+ $null = New-Item -ItemType Directory -Path $resolvedShimPropsDirectory -Force
+
+ @(
+ '',
+ ' ',
+ ' net48',
+ ' latest',
+ ' disable',
+ ' disable',
+ ' true',
+ ' false',
+ ' false',
+ ' 1591',
+ ' $(MSBuildThisFileDirectory)..\..\shims\artifacts\$(Configuration)\',
+ ' false',
+ ' false',
+ ' ',
+ ''
+ ) -join [Environment]::NewLine | Set-Content -LiteralPath $resolvedGeneratedShimPropsPath -Encoding utf8
+
+ foreach ($projectEntry in $shimProjects.GetEnumerator()) {
+ $projectName = [string]$projectEntry.Key
+ $projectConfig = $projectEntry.Value
+ $rootNamespace = if ($projectConfig.RootNamespace) {
+ [string]$projectConfig.RootNamespace
+ }
+ else {
+ $projectName
+ }
+
+ $projectLines = [System.Collections.Generic.List[string]]::new()
+ $projectLines.Add('')
+ $projectLines.Add((' ' -f $resolvedGeneratedShimPropsPath.Replace('&', '&')))
+ $projectLines.Add(' ')
+ $projectLines.Add(" $projectName")
+ $projectLines.Add(" $rootNamespace")
+ $projectLines.Add(' ')
+
+ $sourcePaths = [System.Collections.Generic.List[string]]::new()
+ if ($projectConfig.ContainsKey('SourceFiles')) {
+ $projectSourceRoot = Join-Path $resolvedGeneratedShimSourcesPath $projectName
+ $null = New-Item -ItemType Directory -Path $projectSourceRoot -Force
+
+ foreach ($sourceFile in @($projectConfig.SourceFiles)) {
+ if ($null -eq $sourceFile) {
+ continue
+ }
+
+ $sourceRelativePath = [string]$sourceFile.Path
+ if ([string]::IsNullOrWhiteSpace($sourceRelativePath)) {
+ continue
+ }
+
+ $sourceOutputPath = Join-Path $projectSourceRoot $sourceRelativePath
+ $sourceOutputDirectory = Split-Path -Path $sourceOutputPath -Parent
+ if (-not [string]::IsNullOrWhiteSpace($sourceOutputDirectory)) {
+ $null = New-Item -ItemType Directory -Path $sourceOutputDirectory -Force
+ }
+
+ $sourceContent = if ($sourceFile.ContainsKey('Content')) {
+ [string]$sourceFile.Content
+ }
+ else {
+ [string]$sourceFile.content
+ }
+
+ Set-Content -LiteralPath $sourceOutputPath -Value $sourceContent -Encoding utf8
+ $null = $sourcePaths.Add($sourceOutputPath)
+ }
+ }
+
+ if ($sourcePaths.Count -gt 0) {
+ $projectLines.Add(' ')
+ foreach ($sourcePath in $sourcePaths) {
+ $projectLines.Add((' ' -f $sourcePath.Replace('&', '&')))
+ }
+ $projectLines.Add(' ')
+ }
+
+ if ($projectConfig.ContainsKey('ProjectReferences') -and @($projectConfig.ProjectReferences).Count -gt 0) {
+ $projectLines.Add(' ')
+ foreach ($projectReference in @($projectConfig.ProjectReferences)) {
+ $projectReferenceName = [string]$projectReference
+ $projectReferenceDllPath = Join-Path $resolvedShimOutputDirectory "$projectReferenceName.dll"
+ $projectLines.Add((' ' -f $projectReferenceName.Replace('&', '&')))
+ $projectLines.Add((' {0}' -f $projectReferenceDllPath.Replace('&', '&')))
+ $projectLines.Add(' false')
+ $projectLines.Add(' ')
+ }
+ $projectLines.Add(' ')
+ }
+
+ $projectLines.Add('')
+
+ $generatedProjectPath = Join-Path $resolvedGeneratedShimProjectsPath "$projectName.csproj"
+ $projectLines -join [Environment]::NewLine | Set-Content -LiteralPath $generatedProjectPath -Encoding utf8
+ }
+
+ function Add-ShimProjectBuildOrder {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$ProjectName,
+
+ [Parameter(Mandatory = $true)]
+ [hashtable]$ProjectsByName,
+
+ [Parameter(Mandatory = $true)]
+ [AllowEmptyCollection()]
+ [System.Collections.Generic.HashSet[string]]$Visiting,
+
+ [Parameter(Mandatory = $true)]
+ [AllowEmptyCollection()]
+ [System.Collections.Generic.HashSet[string]]$Visited,
+
+ [Parameter(Mandatory = $true)]
+ [AllowEmptyCollection()]
+ [System.Collections.Generic.List[string]]$BuildOrder
+ )
+
+ if ($Visited.Contains($ProjectName)) {
+ return
+ }
+
+ if (-not $Visiting.Add($ProjectName)) {
+ throw "Detected circular shim dependency involving '$ProjectName'"
+ }
+
+ $projectConfig = $ProjectsByName[$ProjectName]
+ if ($projectConfig -and $projectConfig.ContainsKey('ProjectReferences')) {
+ foreach ($projectReference in @($projectConfig.ProjectReferences)) {
+ $projectReferenceName = [string]$projectReference
+ if (-not $ProjectsByName.ContainsKey($projectReferenceName)) {
+ throw "Shim project '$ProjectName' references undefined shim project '$projectReferenceName'"
+ }
+
+ Add-ShimProjectBuildOrder -ProjectName $projectReferenceName -ProjectsByName $ProjectsByName -Visiting $Visiting -Visited $Visited -BuildOrder $BuildOrder
+ }
+ }
+
+ $null = $Visiting.Remove($ProjectName)
+ $null = $Visited.Add($ProjectName)
+ $BuildOrder.Add($ProjectName)
+ }
+
+ $orderedProjectNames = [System.Collections.Generic.List[string]]::new()
+ $visitingProjects = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
+ $visitedProjects = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
+ foreach ($projectName in $shimProjects.Keys) {
+ Add-ShimProjectBuildOrder -ProjectName ([string]$projectName) -ProjectsByName $shimProjects -Visiting $visitingProjects -Visited $visitedProjects -BuildOrder $orderedProjectNames
+ }
+
+ $projects = foreach ($projectName in $orderedProjectNames) {
+ Get-Item -LiteralPath (Join-Path $resolvedGeneratedShimProjectsPath "$projectName.csproj")
+ }
+ if (-not $projects) {
+ throw "No generated shim projects found under $resolvedGeneratedShimProjectsPath"
+ }
+
+ foreach ($project in $projects) {
+ Write-Host ("Building shim assembly: {0}" -f $project.Name) -ForegroundColor Cyan
+ $shimBuildArgs = 'build', $project.FullName, '-c', $Configuration, '-nologo'
+
+ if ($Rebuild) {
+ $shimBuildArgs += '-t:Rebuild'
+ }
+
+ dotnet @shimBuildArgs
+ if ($LASTEXITCODE -ne 0) {
+ throw ("Shim build failed: {0}" -f $project.FullName)
+ }
+ }
+
+ $resolvedShimOutputPath = (Resolve-Path $ShimOutputPath).Path
+ $previousReferencePath = $env:UNIFIED_ENGINEERING_REFERENCE_PATH
+ $previousFrameworkPathOverride = $env:FrameworkPathOverride
+ $previousTargetFrameworkRootPath = $env:TargetFrameworkRootPath
+ $targetDirectories = foreach ($entry in $Directory) {
+ foreach ($candidate in ($entry -split ',')) {
+ $trimmed = $candidate.Trim().Trim([char[]]@("'", '"'))
+ if (-not [string]::IsNullOrWhiteSpace($trimmed)) {
+ $trimmed
+ }
+ }
+ }
+
+ try {
+ $env:UNIFIED_ENGINEERING_REFERENCE_PATH = $resolvedShimOutputPath
+
+ if (-not $IsWindows) {
+ $netFrameworkReferences = Ensure-NetFrameworkReferenceAssemblies -WorkspaceRoot $repoRoot
+ $env:FrameworkPathOverride = $netFrameworkReferences.FrameworkPathOverride
+ $env:TargetFrameworkRootPath = $netFrameworkReferences.TargetFrameworkRootPath
+
+ Write-Host ("Using .NET Framework reference assemblies from: {0}" -f $env:FrameworkPathOverride) -ForegroundColor Yellow
+ }
+
+ if ($env:TF_BUILD) {
+ Write-Host ("##vso[task.setvariable variable=UNIFIED_ENGINEERING_REFERENCE_PATH]{0}" -f $resolvedShimOutputPath)
+ }
+
+ Write-Host ("Using fake UE references from: {0}" -f $resolvedShimOutputPath) -ForegroundColor Yellow
+
+ foreach ($targetDirectory in $targetDirectories) {
+ Write-Host ("Compiling with fake UE references: {0}" -f $targetDirectory) -ForegroundColor Yellow
+ & $buildScriptPath -Directory $targetDirectory -Configuration $Configuration -SkipPostBuildEvent -Rebuild:$Rebuild
+ if ($LASTEXITCODE -ne 0) {
+ exit $LASTEXITCODE
+ }
+ }
+
+ exit 0
+ }
+ finally {
+ if ([string]::IsNullOrWhiteSpace($previousReferencePath)) {
+ Remove-Item Env:UNIFIED_ENGINEERING_REFERENCE_PATH -ErrorAction SilentlyContinue
+ }
+ else {
+ $env:UNIFIED_ENGINEERING_REFERENCE_PATH = $previousReferencePath
+ }
+
+ if ([string]::IsNullOrWhiteSpace($previousFrameworkPathOverride)) {
+ Remove-Item Env:FrameworkPathOverride -ErrorAction SilentlyContinue
+ }
+ else {
+ $env:FrameworkPathOverride = $previousFrameworkPathOverride
+ }
+
+ if ([string]::IsNullOrWhiteSpace($previousTargetFrameworkRootPath)) {
+ Remove-Item Env:TargetFrameworkRootPath -ErrorAction SilentlyContinue
+ }
+ else {
+ $env:TargetFrameworkRootPath = $previousTargetFrameworkRootPath
+ }
+ }
+ '@
+
+ Set-Content -LiteralPath $generatedRunnerPath -Value $runnerScript -Encoding utf8
+ displayName: Generate fake UE compile helper
+
+ - ${{ if ne(parameters.Rebuild, true) }}:
+ - pwsh: |
+ & '${{ parameters.GeneratedRunnerPath }}' `
+ -Directory '${{ parameters.Directory }}' `
+ -Configuration '${{ parameters.Configuration }}' `
+ -ShimManifestPath '${{ parameters.ShimManifestPath }}' `
+ -GeneratedShimProjectsPath '${{ parameters.GeneratedShimProjectsPath }}' `
+ -GeneratedShimSourcesPath '${{ parameters.GeneratedShimSourcesPath }}' `
+ -GeneratedShimPropsPath '${{ parameters.GeneratedShimPropsPath }}' `
+ -ShimOutputPath '${{ parameters.ShimOutputPath }}'
+ displayName: Run fake UE compile helper
+
+ - ${{ if eq(parameters.Rebuild, true) }}:
+ - pwsh: |
+ & '${{ parameters.GeneratedRunnerPath }}' `
+ -Directory '${{ parameters.Directory }}' `
+ -Configuration '${{ parameters.Configuration }}' `
+ -ShimManifestPath '${{ parameters.ShimManifestPath }}' `
+ -GeneratedShimProjectsPath '${{ parameters.GeneratedShimProjectsPath }}' `
+ -GeneratedShimSourcesPath '${{ parameters.GeneratedShimSourcesPath }}' `
+ -GeneratedShimPropsPath '${{ parameters.GeneratedShimPropsPath }}' `
+ -ShimOutputPath '${{ parameters.ShimOutputPath }}' `
+ -Rebuild
+ displayName: Run fake UE compile helper (Rebuild)
diff --git a/shims/shim-projects.yml b/shims/shim-projects.yml
new file mode 100644
index 0000000..d246f19
--- /dev/null
+++ b/shims/shim-projects.yml
@@ -0,0 +1,1802 @@
+parameters:
+ - name: OutputPath
+ type: string
+ default: $(Pipeline.Workspace)/Source/.tmp/generated-shim-manifest.json
+ - name: Shims
+ type: object
+ default:
+ - name: "Aveva.ApplicationFramework"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/ApplicationFramework.cs"
+ content: |-
+ using System;
+ using System.Collections.Generic;
+
+ namespace Aveva.ApplicationFramework
+ {
+ public interface IAddinInjected
+ {
+ string Name { get; }
+
+ string Description { get; }
+
+ void Start(IDependencyResolver resolver);
+
+ void Start(ServiceManager serviceManager);
+
+ void Stop();
+ }
+
+ public interface IDependencyResolver
+ {
+ }
+
+ public sealed class ServiceManager
+ {
+ }
+
+ public static class DependencyResolver
+ {
+ private static readonly Aveva.ApplicationFramework.Presentation.WindowManager s_windowManager = new Aveva.ApplicationFramework.Presentation.WindowManager();
+ private static readonly Aveva.ApplicationFramework.Presentation.CommandManager s_commandManager = new Aveva.ApplicationFramework.Presentation.CommandManager();
+ private static readonly Dictionary s_services = new Dictionary
+ {
+ { typeof(Aveva.ApplicationFramework.Presentation.IWindowManager), s_windowManager },
+ { typeof(Aveva.ApplicationFramework.Presentation.ICommandManager), s_commandManager },
+ { typeof(Aveva.ApplicationFramework.Presentation.ICommandBarManager), s_windowManager.CommandBarManager },
+ };
+
+ public static T GetImplementationOf() where T : class
+ {
+ if (s_services.TryGetValue(typeof(T), out object service))
+ {
+ return service as T;
+ }
+
+ return null;
+ }
+ }
+
+ public static class Log
+ {
+ private static readonly LoggerProxy s_logger = new LoggerProxy();
+
+ public static LoggerProxy Logger()
+ {
+ return s_logger;
+ }
+ }
+
+ public sealed class LoggerProxy
+ {
+ public void MessageFormat(string format, params object[] args)
+ {
+ }
+ }
+ }
+ projectReferences:
+ - "Aveva.ApplicationFramework.Presentation"
+ - name: "Aveva.ApplicationFramework.Implementation"
+ rootNamespace: ""
+ sourceFiles: []
+ projectReferences: []
+ - name: "Aveva.ApplicationFramework.Presentation"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/ApplicationFrameworkPresentation.cs"
+ content: |-
+ using System;
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.Windows.Forms;
+
+ namespace Aveva.ApplicationFramework.Presentation
+ {
+ public class Command
+ {
+ public string Key { get; set; }
+
+ public IList List { get; } = new List();
+
+ public object Value { get; set; }
+
+ public bool Checked { get; set; }
+
+ public virtual void Execute()
+ {
+ }
+ }
+
+ public enum DockedPosition
+ {
+ Left,
+ Right,
+ Top,
+ Bottom,
+ }
+
+ public class DockedWindow
+ {
+ public event CancelEventHandler Closing;
+
+ public event EventHandler Closed;
+
+ public string Key { get; set; }
+
+ public string Title { get; set; }
+
+ public Control Content { get; set; }
+
+ public DockedPosition Position { get; set; }
+
+ public int Width { get; set; }
+
+ public bool SaveLayout { get; set; }
+
+ public bool Visible { get; private set; }
+
+ public void Show()
+ {
+ Visible = true;
+ }
+
+ public void Hide()
+ {
+ Visible = false;
+ }
+
+ public void Close()
+ {
+ CancelEventArgs args = new CancelEventArgs();
+ Closing?.Invoke(this, args);
+ if (args.Cancel)
+ {
+ return;
+ }
+
+ Visible = false;
+ Closed?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ public interface IWindowManager
+ {
+ event EventHandler WindowLayoutLoaded;
+
+ ICommandBarManager CommandBarManager { get; }
+
+ DockedWindow CreateDockedWindow(string key, string title, Control control, DockedPosition position);
+ }
+
+ public interface ICommandManager
+ {
+ CommandCollection Commands { get; }
+ }
+
+ public interface ICommandBarManager
+ {
+ event EventHandler UILoaded;
+
+ ToolCollection RootTools { get; }
+ }
+
+ public interface ITool
+ {
+ string Key { get; set; }
+
+ bool Checked { get; set; }
+
+ event EventHandler ToolClick;
+ }
+
+ public sealed class CommandCollection : List
+ {
+ }
+
+ public class CommandManager : ICommandManager
+ {
+ public CommandCollection Commands { get; } = new CommandCollection();
+ }
+
+ public class WindowManager : IWindowManager
+ {
+ public event EventHandler WindowLayoutLoaded;
+
+ public ICommandBarManager CommandBarManager { get; } = new CommandBarManager();
+
+ public DockedWindow CreateDockedWindow(string key, string title, Control control, DockedPosition position)
+ {
+ WindowLayoutLoaded?.Invoke(this, EventArgs.Empty);
+ return new DockedWindow
+ {
+ Key = key,
+ Title = title,
+ Content = control,
+ Position = position,
+ };
+ }
+ }
+
+ public class CommandBarManager : ICommandBarManager
+ {
+ public event EventHandler UILoaded;
+
+ public ToolCollection RootTools { get; } = new ToolCollection();
+
+ public void RaiseUiLoaded()
+ {
+ UILoaded?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ public class ToolEventArgs : EventArgs
+ {
+ public string Key { get; set; }
+ }
+
+ public delegate void ToolEventHandler(object sender, ToolEventArgs args);
+
+ public class ToolBase : ITool
+ {
+ public string Key { get; set; }
+
+ public bool Checked { get; set; }
+
+ public event EventHandler ToolClick;
+
+ protected void RaiseToolClick()
+ {
+ ToolClick?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ public class ButtonTool : ToolBase
+ {
+ }
+
+ public class StateButtonTool : ToolBase
+ {
+ }
+
+ public class MenuTool : ToolBase
+ {
+ public bool IsContextMenu { get; set; }
+
+ public ToolCollection Tools { get; } = new ToolCollection();
+ }
+
+ public class ToolCollection
+ {
+ private readonly Dictionary _items = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ public event ToolEventHandler ToolAdded;
+
+ public ITool this[string key] => _items.TryGetValue(key, out ITool tool) ? tool : null;
+
+ public bool Contains(string key)
+ {
+ return _items.ContainsKey(key);
+ }
+
+ public MenuTool AddMenuTool(string key, string caption, object image, string category)
+ {
+ MenuTool tool = new MenuTool { Key = key };
+ _items[key] = tool;
+ ToolAdded?.Invoke(this, new ToolEventArgs { Key = key });
+ return tool;
+ }
+
+ public ButtonTool AddButtonTool(string key, string caption, object image, string category)
+ {
+ ButtonTool tool = new ButtonTool { Key = key };
+ _items[key] = tool;
+ ToolAdded?.Invoke(this, new ToolEventArgs { Key = key });
+ return tool;
+ }
+
+ public ITool AddTool(string key)
+ {
+ if (_items.TryGetValue(key, out ITool existing))
+ {
+ return existing;
+ }
+
+ ButtonTool tool = new ButtonTool { Key = key };
+ _items[key] = tool;
+ ToolAdded?.Invoke(this, new ToolEventArgs { Key = key });
+ return tool;
+ }
+ }
+
+ public static class MessageBoxEx
+ {
+ public static DialogResult Show(string text)
+ {
+ return DialogResult.OK;
+ }
+
+ public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
+ {
+ return DialogResult.OK;
+ }
+ }
+ }
+ projectReferences: []
+ - name: "Aveva.Core.Database"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/Database.cs"
+ content: |-
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using Aveva.Core.Geometry;
+ using Aveva.Core.Utilities.Messaging;
+
+ namespace Aveva.Core.Database
+ {
+ public class DbAttribute
+ {
+ public DbAttribute()
+ {
+ }
+
+ public DbAttribute(string name)
+ {
+ Name = name;
+ }
+
+ public string Name { get; set; }
+
+ public static DbAttribute GetDbAttribute(string name)
+ {
+ return new DbAttribute(name);
+ }
+ }
+
+ public static class DbAttributeInstance
+ {
+ public static readonly DbAttribute NAME = new DbAttribute(nameof(NAME));
+ public static readonly DbAttribute DESC = new DbAttribute(nameof(DESC));
+ public static readonly DbAttribute BUIL = new DbAttribute(nameof(BUIL));
+ public static readonly DbAttribute REV = new DbAttribute(nameof(REV));
+ public static readonly DbAttribute PRES = new DbAttribute(nameof(PRES));
+ public static readonly DbAttribute HREF = new DbAttribute(nameof(HREF));
+ public static readonly DbAttribute LNTP = new DbAttribute(nameof(LNTP));
+ public static readonly DbAttribute HDIR = new DbAttribute(nameof(HDIR));
+ public static readonly DbAttribute POS = new DbAttribute(nameof(POS));
+ public static readonly DbAttribute ORI = new DbAttribute(nameof(ORI));
+ public static readonly DbAttribute FLNN = new DbAttribute(nameof(FLNN));
+ public static readonly DbAttribute FLNM = new DbAttribute(nameof(FLNM));
+ public static readonly DbAttribute AREA = new DbAttribute(nameof(AREA));
+ public static readonly DbAttribute CREF = new DbAttribute(nameof(CREF));
+ public static readonly DbAttribute ISNAME = new DbAttribute(nameof(ISNAME));
+ public static readonly DbAttribute MEMB = new DbAttribute(nameof(MEMB));
+ public static readonly DbAttribute DIAM = new DbAttribute(nameof(DIAM));
+ public static readonly DbAttribute LCLM = new DbAttribute(nameof(LCLM));
+ public static readonly DbAttribute LCLMH = new DbAttribute(nameof(LCLMH));
+ public static readonly DbAttribute XLEN = new DbAttribute(nameof(XLEN));
+ public static readonly DbAttribute YLEN = new DbAttribute(nameof(YLEN));
+ public static readonly DbAttribute ZLEN = new DbAttribute(nameof(ZLEN));
+ }
+
+ public class DbElementType
+ {
+ public DbElementType()
+ {
+ }
+
+ public DbElementType(string name)
+ {
+ Name = name;
+ }
+
+ public string Name { get; set; }
+
+ public static DbElementType GetElementType(int hash)
+ {
+ return new DbElementType(hash.ToString());
+ }
+
+ public static DbElementType GetElementType(string name)
+ {
+ return new DbElementType(name);
+ }
+
+ public DbAttribute[] SystemAttributes()
+ {
+ return Array.Empty();
+ }
+ }
+
+ public static class DbElementTypeInstance
+ {
+ public static readonly DbElementType SITE = new DbElementType(nameof(SITE));
+ public static readonly DbElementType ZONE = new DbElementType(nameof(ZONE));
+ public static readonly DbElementType EQUIPMENT = new DbElementType(nameof(EQUIPMENT));
+ public static readonly DbElementType CYLINDER = new DbElementType(nameof(CYLINDER));
+ public static readonly DbElementType BOX = new DbElementType(nameof(BOX));
+ public static readonly DbElementType NOZZLE = new DbElementType(nameof(NOZZLE));
+ public static readonly DbElementType PIPE = new DbElementType(nameof(PIPE));
+ public static readonly DbElementType BRANCH = new DbElementType(nameof(BRANCH));
+ public static readonly DbElementType GASKET = new DbElementType(nameof(GASKET));
+ public static readonly DbElementType ELBOW = new DbElementType(nameof(ELBOW));
+ public static readonly DbElementType TEE = new DbElementType(nameof(TEE));
+ }
+
+ public class DbElement
+ {
+ public static DbElement GetElement(string name)
+ {
+ return new DbElement
+ {
+ Name = name,
+ IsValid = true,
+ };
+ }
+
+ public string Name { get; set; }
+
+ public bool IsValid { get; set; } = true;
+
+ public DbElement Previous { get; set; } = new DbElement(false);
+
+ public DbElement()
+ {
+ }
+
+ private DbElement(bool valid)
+ {
+ IsValid = valid;
+ }
+
+ public void Delete()
+ {
+ }
+
+ public DbElement Create(int position, DbElementType elementType)
+ {
+ return new DbElement();
+ }
+
+ public DbElement CreateAfter(DbElementType elementType)
+ {
+ return new DbElement();
+ }
+
+ public DbElement CreateFirst(DbElementType elementType)
+ {
+ return new DbElement();
+ }
+
+ public DbElement CreateLast(DbElementType elementType)
+ {
+ return new DbElement();
+ }
+
+ public void SetAttribute(DbAttribute attribute, object value)
+ {
+ }
+
+ public string GetString(DbAttribute attribute)
+ {
+ return string.Empty;
+ }
+
+ public bool GetBool(DbAttribute attribute)
+ {
+ return false;
+ }
+
+ public int GetInteger(DbAttribute attribute)
+ {
+ return 0;
+ }
+
+ public double GetDouble(DbAttribute attribute)
+ {
+ return 0d;
+ }
+
+ public DbElement GetElement(DbAttribute attribute)
+ {
+ return new DbElement();
+ }
+
+ public Direction GetDirection(DbAttribute attribute)
+ {
+ return new Direction();
+ }
+
+ public Position GetPosition(DbAttribute attribute)
+ {
+ return Position.Create(0d, 0d, 0d);
+ }
+
+ public Orientation GetOrientation(DbAttribute attribute)
+ {
+ return new Orientation();
+ }
+
+ public string GetAsString(DbAttribute attribute)
+ {
+ return string.Empty;
+ }
+
+ public DbAttribute[] GetAttributes()
+ {
+ return Array.Empty();
+ }
+
+ public void Copy(DbElement element)
+ {
+ }
+
+ public void CopyHierarchy(DbElement element, DbCopyOption options)
+ {
+ }
+
+ public DbElement FirstMember()
+ {
+ return new DbElement();
+ }
+
+ public DbElement FirstMember(DbElementType type)
+ {
+ return new DbElement();
+ }
+
+ public void InsertAfterLast(DbElement element)
+ {
+ }
+
+ public void InsertAfter(DbElement element)
+ {
+ }
+
+ public DbElement Next()
+ {
+ return new DbElement();
+ }
+
+ public DbElement Next(DbElementType type)
+ {
+ return new DbElement();
+ }
+
+ public DbElement[] Members()
+ {
+ return Array.Empty();
+ }
+
+ public DbElement[] Members(DbElementType type)
+ {
+ return Array.Empty();
+ }
+
+ public DbElement Member(int index)
+ {
+ return new DbElement();
+ }
+
+ public DbElement[] GetElementArray(DbAttribute attribute, DbElementType type)
+ {
+ return Array.Empty();
+ }
+
+ public double EvaluateDouble(DbExpression expression, DbAttributeUnit unit)
+ {
+ return 0d;
+ }
+
+ public void SetRule(DbAttribute attribute, DbRule rule)
+ {
+ }
+
+ public DbRule GetRule(DbAttribute attribute)
+ {
+ return new DbRule();
+ }
+
+ public void DeleteRule(DbAttribute attribute)
+ {
+ }
+
+ public bool ExistRule(DbAttribute attribute)
+ {
+ return false;
+ }
+
+ public bool VerifyRule(DbAttribute attribute)
+ {
+ return false;
+ }
+
+ public void ExecuteRule(DbAttribute attribute)
+ {
+ }
+
+ public void ExecuteAllRules()
+ {
+ }
+
+ public void PropagateRules(DbAttribute attribute)
+ {
+ }
+
+ public void Claim()
+ {
+ }
+
+ public void Release()
+ {
+ }
+
+ public void ClaimHierarchy()
+ {
+ }
+
+ public void ReleaseHierarchy()
+ {
+ }
+
+ public DbElementType GetElementType()
+ {
+ return new DbElementType();
+ }
+
+ public void ChangeType(DbElementType type)
+ {
+ }
+
+ public bool AtDefault(DbAttribute attribute)
+ {
+ return true;
+ }
+
+ public bool IsDescendant(DbElement element)
+ {
+ return false;
+ }
+
+ public void SetAttributeDefault(DbAttribute attribute)
+ {
+ }
+ }
+
+ public class DBElementCollection : IEnumerable
+ {
+ public DBElementCollection(DbElement root)
+ {
+ }
+
+ public DBElementCollection(DbElement root, object filter)
+ {
+ }
+
+ public bool IncludeRoot { get; set; }
+
+ public object Filter { get; set; }
+
+ public DBElementEnumerator GetEnumerator()
+ {
+ return new DBElementEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+
+ public class DBElementEnumerator : IEnumerator
+ {
+ public DbElement Current => new DbElement();
+
+ object IEnumerator.Current => Current;
+
+ public bool MoveNext()
+ {
+ return false;
+ }
+
+ public void Reset()
+ {
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class DbCopyOption
+ {
+ public string FromName { get; set; }
+
+ public string ToName { get; set; }
+
+ public bool Rename { get; set; }
+ }
+
+ public class DbExpression
+ {
+ public static DbExpression Parse(string expression)
+ {
+ return new DbExpression();
+ }
+ }
+
+ public enum DbAttributeUnit
+ {
+ DIST,
+ }
+
+ public enum DbRuleStatus
+ {
+ DYNAMIC,
+ }
+
+ public enum DbExpressionType
+ {
+ REAL,
+ }
+
+ public class DbRule
+ {
+ public static DbRule CreateDbRule(DbExpression expression, DbRuleStatus status, DbExpressionType expressionType)
+ {
+ return new DbRule();
+ }
+ }
+
+ public class Project
+ {
+ public static Project CurrentProject { get; } = new Project();
+
+ public string Name { get; set; } = string.Empty;
+
+ public string UserName { get; set; } = string.Empty;
+
+ public bool IsOpen()
+ {
+ return false;
+ }
+
+ public void Open(string project, string username, string password)
+ {
+ Name = project;
+ UserName = username;
+ }
+
+ public void Close()
+ {
+ }
+ }
+
+ public class MDB
+ {
+ public static MDB CurrentMDB { get; } = new MDB();
+
+ public string Name { get; set; } = string.Empty;
+
+ public void SaveWork(string description)
+ {
+ }
+
+ public void CloseMDB()
+ {
+ }
+
+ public void Open(MDBSetup setup, out PdmsMessage error)
+ {
+ error = new PdmsMessage();
+ }
+
+ public Db[] GetDBArray(DbType type)
+ {
+ return Array.Empty();
+ }
+
+ public DbElement GetFirstWorld(DbType type)
+ {
+ return new DbElement();
+ }
+
+ public void QuitWork()
+ {
+ }
+ }
+
+ public class MDBSetup
+ {
+ public static MDBSetup CreateMDBSetup(string name, out PdmsMessage error)
+ {
+ error = new PdmsMessage();
+ return new MDBSetup();
+ }
+ }
+
+ public class Db
+ {
+ public string Name { get; set; } = string.Empty;
+ }
+
+ public enum DbType
+ {
+ Design,
+ }
+
+ public class NameTable : IEnumerable
+ {
+ public static NameTable GetNameTable(Db db, DbAttribute attribute, string prefix, string suffix)
+ {
+ return new NameTable();
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return ((IEnumerable)Array.Empty()).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+
+ public class ElementTreeNavigator
+ {
+ public ElementTreeNavigator(DbElement root, object filter)
+ {
+ }
+
+ public DbElement FirstMemberInScan(DbElement element)
+ {
+ return new DbElement();
+ }
+
+ public DbElement NextInScan(DbElement element)
+ {
+ return new DbElement();
+ }
+
+ public DbElement Parent(DbElement element)
+ {
+ return new DbElement();
+ }
+
+ public DbElement[] MembersInScan(DbElement element)
+ {
+ return Array.Empty();
+ }
+ }
+
+ public class DbQualifier
+ {
+ }
+
+ public static class DbPseudoAttribute
+ {
+ public delegate double GetDoubleDelegate(DbElement element, DbAttribute attribute, DbQualifier qualifier);
+
+ public static void AddGetDoubleAttribute(DbAttribute attribute, DbElementType type, GetDoubleDelegate callback)
+ {
+ }
+ }
+
+ public static class DbPostElementChange
+ {
+ public delegate void PostCreateDelegate(DbElement element);
+
+ public delegate void PreDeleteDelegate(DbElement element);
+
+ public delegate void PostMoveDelegate(DbElement element, DbElement oldOwner, int oldPosition);
+
+ public delegate void PostAttributeChangeDelegate(DbElement element, DbAttribute attribute);
+
+ public delegate void PostRefAttributeChangeDelegate(DbElement element, DbAttribute attribute, DbElement oldReference);
+
+ public static void AddPostCreateElement(DbElementType type, PostCreateDelegate callback)
+ {
+ }
+
+ public static void AddPreDeleteElement(DbElementType type, PreDeleteDelegate callback)
+ {
+ }
+
+ public static void AddPostMoveElement(DbElementType type, PostMoveDelegate callback)
+ {
+ }
+
+ public static void AddPostAttributeChange(DbAttribute attribute, PostAttributeChangeDelegate callback)
+ {
+ }
+
+ public static void AddPostRefAttributeChange(DbAttribute attribute, PostRefAttributeChangeDelegate callback)
+ {
+ }
+ }
+
+ public class Spatial
+ {
+ public static Spatial Instance { get; } = new Spatial();
+
+ public DbElement[] ElementsInBox(LimitsBox limitsBox, bool fullyWithin)
+ {
+ return Array.Empty();
+ }
+
+ public DbElement[] ElementsInElementBox(DbElement element, bool fullyWithin)
+ {
+ return Array.Empty();
+ }
+
+ public bool ElementInBox(DbElement element, LimitsBox limitsBox)
+ {
+ return false;
+ }
+
+ public bool ElementInElementBox(DbElement first, DbElement second)
+ {
+ return false;
+ }
+ }
+ }
+ projectReferences:
+ - "Aveva.Core.Geometry"
+ - "Aveva.Core.Utilities"
+ - name: "Aveva.Core.Database.Filters"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/Filters.cs"
+ content: |-
+ using Aveva.Core.Database;
+
+ namespace Aveva.Core.Database.Filters
+ {
+ public class TypeFilter
+ {
+ public TypeFilter()
+ {
+ }
+
+ public TypeFilter(DbElementType type)
+ {
+ }
+
+ public void Add(DbElementType type)
+ {
+ }
+
+ public DbElement FirstMember(DbElement element)
+ {
+ return new DbElement();
+ }
+
+ public DbElement Next(DbElement element)
+ {
+ return new DbElement();
+ }
+
+ public DbElement[] Members(DbElement element)
+ {
+ return System.Array.Empty();
+ }
+
+ public DbElement Parent(DbElement element)
+ {
+ return new DbElement();
+ }
+ }
+
+ public class CompoundFilter
+ {
+ public void AddShow(object filter)
+ {
+ }
+ }
+
+ public class AndFilter : CompoundFilter
+ {
+ public void Add(object filter)
+ {
+ }
+
+ public bool Valid(DbElement element)
+ {
+ return true;
+ }
+ }
+
+ public class TrueFilter
+ {
+ }
+
+ public class AttributeTrueFilter
+ {
+ public AttributeTrueFilter(DbAttribute attribute)
+ {
+ }
+
+ public bool Valid(DbElement element)
+ {
+ return true;
+ }
+ }
+
+ public class BelowOrAtType
+ {
+ public BelowOrAtType(DbElementType type)
+ {
+ }
+
+ public bool ScanBelow(DbElement element)
+ {
+ return true;
+ }
+ }
+ }
+ projectReferences:
+ - "Aveva.Core.Database"
+ - name: "Aveva.Core.FlexibleExplorer.Control"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/FlexibleExplorer.cs"
+ content: |-
+ using System.Windows.Forms;
+ using Aveva.ApplicationFramework;
+ using Aveva.Core.Presentation;
+
+ namespace Aveva.Core.FlexibleExplorer
+ {
+ public class FlexibleExplorer : Control
+ {
+ public FlexibleExplorer(IDependencyResolver dependencyResolver, object viewContext, bool useDefaultImages)
+ {
+ }
+
+ public BorderStyle BorderStyle { get; set; }
+
+ public bool DisplayCheckBox { get; set; }
+
+ public bool DisplayLockIndicator { get; set; }
+
+ public bool DisplayClaimIndicator { get; set; }
+
+ public bool DisplayReadOnlyIndicator { get; set; }
+
+ public Aveva.Core.FlexibleExplorer.IFlexibleExplorerRefreshManager CustomRefreshManager { get; set; }
+
+ public void MultiSelect(bool enabled)
+ {
+ }
+
+ public void CreateRoots(object rootDefinitions, bool preserveExisting)
+ {
+ }
+ }
+ }
+ projectReferences:
+ - "Aveva.ApplicationFramework"
+ - "Aveva.Core.FlexibleExplorer.Core"
+ - "Aveva.Core.Presentation"
+ - name: "Aveva.Core.FlexibleExplorer.Core"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/FlexibleExplorerCore.cs"
+ content: |-
+ using System.IO;
+ using Aveva.Core.Presentation;
+
+ namespace Aveva.Core.FlexibleExplorer
+ {
+ public enum RefreshOperation
+ {
+ None,
+ NodesOnly,
+ NodesWithCollections,
+ WholeTree,
+ }
+
+ public class RefreshArguments
+ {
+ public DbChangesEventArgs DbChangesEventArgs { get; set; }
+
+ public object DbRawChanges { get; set; }
+
+ public RefreshOperation RefreshType { get; set; }
+ }
+
+ public interface IFlexibleExplorerRefreshManager
+ {
+ RefreshArguments GetRefreshType(RefreshArguments refreshArguments);
+ }
+
+ public class XmlDefinitionProvider
+ {
+ public XmlDefinitionProvider(string xmlContent, string fileName)
+ {
+ }
+
+ public object RootDefinitions { get; } = new object[0];
+
+ public static string GetLocalFile(string path)
+ {
+ return File.Exists(path) ? path : null;
+ }
+ }
+ }
+ projectReferences:
+ - "Aveva.Core.Presentation"
+ - name: "Aveva.Core.Geometry"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/Geometry.cs"
+ content: |-
+ namespace Aveva.Core.Geometry
+ {
+ public class Direction
+ {
+ public static Direction Create(double x, double y, double z)
+ {
+ return new Direction();
+ }
+ }
+
+ public class Position
+ {
+ public double X { get; set; }
+
+ public double Y { get; set; }
+
+ public double Z { get; set; }
+
+ public static Position Create(double x, double y, double z)
+ {
+ return new Position
+ {
+ X = x,
+ Y = y,
+ Z = z,
+ };
+ }
+ }
+
+ public class Orientation
+ {
+ public static Orientation Create(Direction xAxis, Direction yAxis)
+ {
+ return new Orientation();
+ }
+ }
+
+ public class LimitsBox
+ {
+ public static LimitsBox Create(Position first, Position second)
+ {
+ return new LimitsBox();
+ }
+ }
+ }
+ projectReferences: []
+ - name: "Aveva.Core.Implementation"
+ rootNamespace: ""
+ sourceFiles: []
+ projectReferences: []
+ - name: "Aveva.Core.Presentation"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/Presentation.cs"
+ content: |-
+ using System;
+ using System.Collections;
+ using System.Windows.Forms;
+ using Aveva.ApplicationFramework.Presentation;
+ using Aveva.Core.Database;
+ using Aveva.Core.PMLNet;
+
+ namespace Aveva.Core.Presentation
+ {
+ public delegate void CurrentElementChangedEventHandler(object sender, CurrentElementChangedEventArgs e);
+
+ public class CurrentElementChangedEventArgs : EventArgs
+ {
+ public DbElement Element { get; set; } = new DbElement();
+ }
+
+ public static class CurrentElement
+ {
+ private static DbElement s_element = new DbElement();
+
+ public static event CurrentElementChangedEventHandler CurrentElementChanged;
+
+ public static DbElement Element
+ {
+ get => s_element;
+ set
+ {
+ s_element = value;
+ CurrentElementChanged?.Invoke(null, new CurrentElementChangedEventArgs { Element = value });
+ }
+ }
+ }
+
+ public delegate void DbChangesEventHandler(object sender, DbChangesEventArgs e);
+
+ public class DbChangesEventArgs : EventArgs
+ {
+ public ChangeList ChangeList { get; } = new ChangeList();
+ }
+
+ public class ChangeList
+ {
+ public DbElement[] GetCreations()
+ {
+ return Array.Empty();
+ }
+
+ public DbElement[] GetDeletions()
+ {
+ return Array.Empty();
+ }
+
+ public DbElement[] GetMemberChanges()
+ {
+ return Array.Empty();
+ }
+
+ public DbElement[] GetModified()
+ {
+ return Array.Empty();
+ }
+ }
+
+ public static class DatabaseService
+ {
+ public static event DbChangesEventHandler Changes;
+
+ public static void RaiseChanges()
+ {
+ Changes?.Invoke(null, new DbChangesEventArgs());
+ }
+ }
+
+ public class NetDataSource
+ {
+ public NetDataSource(string tableName, Hashtable attributes, Hashtable titles, Hashtable items)
+ {
+ }
+ }
+
+ public class NetGridControl : Control
+ {
+ public bool allGridEvents { get; set; }
+
+ public bool ColumnExcelFilter { get; set; }
+
+ public bool ColumnSummaries { get; set; }
+
+ public bool EditableGrid { get; set; }
+
+ public bool ErrorIcon { get; set; }
+
+ public bool ExtendLastColumn { get; set; }
+
+ public bool FixedHeaders { get; set; }
+
+ public bool FixedRows { get; set; }
+
+ public int GridHeight { get; set; }
+
+ public bool HeaderSort { get; set; }
+
+ public bool HideGroupByBox { get; set; }
+
+ public bool OutlookGroupStyle { get; set; }
+
+ public bool RowAddDeleteGrid { get; set; }
+
+ public bool ScrollSelectedRowToView { get; set; }
+
+ public bool SingleRowSelection { get; set; }
+
+ public bool Updates { get; set; }
+
+ public MenuTool PopupMenu { get; set; }
+
+ public MenuTool PopupMenuHeader { get; set; }
+
+ public event PMLNetDelegate.PMLNetEventHandler AfterSelectChange;
+
+ public void BindToDataSource(NetDataSource dataSource)
+ {
+ }
+
+ public void setNameColumnImage()
+ {
+ }
+
+ public void saveGridToExcel(string path)
+ {
+ }
+
+ public void printPreview()
+ {
+ }
+
+ public void setRowColor(double row, string colour)
+ {
+ }
+ }
+ }
+ projectReferences:
+ - "Aveva.ApplicationFramework.Presentation"
+ - "Aveva.Core.Database"
+ - "PMLNet"
+ - name: "Aveva.Core.Utilities"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/Utilities.cs"
+ content: |-
+ using System;
+
+ namespace Aveva.Core.Utilities.AvevaConnect
+ {
+ public interface IAvevaConnectAuthenticationManager
+ {
+ }
+
+ public static class CoreConnectManager
+ {
+ public static string GetAccessToken()
+ {
+ return string.Empty;
+ }
+ }
+ }
+
+ namespace Aveva.Core.Utilities.Messaging
+ {
+ public class PdmsMessage
+ {
+ }
+
+ public class PdmsError
+ {
+ public string MessageText()
+ {
+ return string.Empty;
+ }
+ }
+
+ public class PdmsException : Exception
+ {
+ public PdmsError Error { get; } = new PdmsError();
+
+ public PdmsException()
+ {
+ }
+
+ public PdmsException(string message) : base(message)
+ {
+ }
+ }
+ }
+
+ namespace Aveva.Core.Utilities.Undo
+ {
+ public class UndoTransaction
+ {
+ public static UndoTransaction GetUndoTransaction()
+ {
+ return new UndoTransaction();
+ }
+
+ public void StartTransaction(string description)
+ {
+ }
+
+ public void EndTransaction()
+ {
+ }
+
+ public static void PerformUndo()
+ {
+ }
+
+ public static void PerformRedo()
+ {
+ }
+ }
+
+ public abstract class UndoSubscriber
+ {
+ public virtual object GetBeginState()
+ {
+ return null;
+ }
+
+ public virtual object GetEndState()
+ {
+ return null;
+ }
+
+ public virtual void RestoreBeginState(object state)
+ {
+ }
+
+ public virtual void RestoreEndState(object state)
+ {
+ }
+ }
+
+ public static class UndoCaretaker
+ {
+ public static void RegisterUndoSubscriber(UndoSubscriber subscriber)
+ {
+ }
+
+ public static void RemoveUndoSubscriber(UndoSubscriber subscriber)
+ {
+ }
+ }
+ }
+
+ namespace Aveva.Core.Utilities.CommandLine
+ {
+ public static class Command
+ {
+ public static void Update()
+ {
+ }
+ }
+ }
+ projectReferences: []
+ - name: "Aveva.Core3D.Clasher"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/Clasher.cs"
+ content: |-
+ using System;
+ using Aveva.Core.Database;
+ using Aveva.Core.Geometry;
+
+ namespace Aveva.Core3D.Clasher
+ {
+ public enum SpatialMapStatus
+ {
+ Unknown,
+ Complete,
+ }
+
+ public class SpatialMap
+ {
+ public static SpatialMap Instance { get; } = new SpatialMap();
+
+ public SpatialMapStatus CheckSpatialMap(Db db)
+ {
+ return SpatialMapStatus.Complete;
+ }
+
+ public void BuildSpatialMap()
+ {
+ }
+ }
+
+ public class ClashOptions
+ {
+ public bool IncludeTouches { get; set; }
+
+ public double Clearance { get; set; }
+
+ public static ClashOptions Create()
+ {
+ return new ClashOptions();
+ }
+ }
+
+ public class ObstructionList
+ {
+ public static ObstructionList Create()
+ {
+ return new ObstructionList();
+ }
+ }
+
+ public class ClashSet
+ {
+ public Clash[] Clashes { get; set; } = Array.Empty();
+
+ public static ClashSet Create()
+ {
+ return new ClashSet();
+ }
+ }
+
+ public class Clasher
+ {
+ public static Clasher Instance { get; } = new Clasher();
+
+ public bool CheckAll(ClashOptions options, ObstructionList obstructions, ClashSet clashSet)
+ {
+ return false;
+ }
+ }
+
+ public class Clash
+ {
+ public DbElement First { get; set; } = new DbElement();
+
+ public DbElement Second { get; set; } = new DbElement();
+
+ public ClashType Type { get; set; }
+
+ public Position ClashPosition { get; set; } = Position.Create(0d, 0d, 0d);
+ }
+
+ public enum ClashType
+ {
+ None,
+ }
+ }
+ projectReferences:
+ - "Aveva.Core.Database"
+ - "Aveva.Core.Geometry"
+ - name: "GridControl"
+ rootNamespace: ""
+ sourceFiles: []
+ projectReferences: []
+ - name: "Microsoft.Web.WebView2.Core"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/WebView2Core.cs"
+ content: |-
+ using System;
+
+ namespace Microsoft.Web.WebView2.Core
+ {
+ public class CoreWebView2Settings
+ {
+ public bool AreDefaultContextMenusEnabled { get; set; }
+
+ public bool IsStatusBarEnabled { get; set; }
+
+ public bool IsZoomControlEnabled { get; set; }
+
+ public bool AreDevToolsEnabled { get; set; }
+ }
+
+ public class CoreWebView2NavigationStartingEventArgs : EventArgs
+ {
+ }
+
+ public class CoreWebView2NavigationCompletedEventArgs : EventArgs
+ {
+ }
+
+ public class CoreWebView2
+ {
+ public CoreWebView2Settings Settings { get; } = new CoreWebView2Settings();
+
+ public event EventHandler NavigationStarting;
+
+ public event EventHandler NavigationCompleted;
+ }
+ }
+ projectReferences: []
+ - name: "Microsoft.Web.WebView2.WinForms"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/WebView2.cs"
+ content: |-
+ using System;
+ using System.ComponentModel;
+ using System.Threading.Tasks;
+ using System.Windows.Forms;
+
+ namespace Microsoft.Web.WebView2.WinForms
+ {
+ public class WebView2 : Control, ISupportInitialize
+ {
+ public Microsoft.Web.WebView2.Core.CoreWebView2 CoreWebView2 { get; private set; } = new Microsoft.Web.WebView2.Core.CoreWebView2();
+
+ public double ZoomFactor { get; set; }
+
+ public Uri Source { get; set; }
+
+ public Task EnsureCoreWebView2Async(object environment)
+ {
+ if (CoreWebView2 == null)
+ {
+ CoreWebView2 = new Microsoft.Web.WebView2.Core.CoreWebView2();
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public void BeginInit()
+ {
+ }
+
+ public void EndInit()
+ {
+ }
+ }
+ }
+ projectReferences:
+ - "Microsoft.Web.WebView2.Core"
+ - name: "Newtonsoft.Json"
+ rootNamespace: ""
+ sourceFiles:
+ - path: "src/Json.cs"
+ content: |-
+ using System.Collections;
+ using System.Collections.Generic;
+
+ namespace Newtonsoft.Json.Linq
+ {
+ public enum JTokenType
+ {
+ Object,
+ Array,
+ String,
+ Integer,
+ Float,
+ Boolean,
+ Null,
+ }
+
+ public abstract class JToken
+ {
+ public abstract JTokenType Type { get; }
+
+ public static JToken Parse(string json)
+ {
+ return new JObject();
+ }
+
+ public virtual T Value()
+ {
+ return default(T);
+ }
+
+ public override string ToString()
+ {
+ return string.Empty;
+ }
+ }
+
+ public sealed class JObject : JToken
+ {
+ public override JTokenType Type => JTokenType.Object;
+
+ public IEnumerable Properties()
+ {
+ return new JProperty[0];
+ }
+ }
+
+ public sealed class JArray : JToken, IEnumerable
+ {
+ public override JTokenType Type => JTokenType.Array;
+
+ public int Count => 0;
+
+ public IEnumerator GetEnumerator()
+ {
+ return ((IEnumerable)new JToken[0]).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+
+ public sealed class JProperty
+ {
+ public string Name { get; set; } = string.Empty;
+
+ public JToken Value { get; set; } = new JValue();
+ }
+
+ public sealed class JValue : JToken
+ {
+ public override JTokenType Type => JTokenType.String;
+
+ public override T Value()
+ {
+ return default(T);
+ }
+ }
+ }
+ projectReferences: []
+ - name: "PMLNet"
+ rootNamespace: "Aveva.Core.PMLNet"
+ sourceFiles:
+ - path: "src/PmlNet.cs"
+ content: |-
+ using System;
+ using System.Collections;
+
+ namespace Aveva.Core.PMLNet
+ {
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
+ public sealed class PMLNetCallableAttribute : Attribute
+ {
+ }
+
+ public static class PMLNetDelegate
+ {
+ public delegate void PMLNetEventHandler(ArrayList args);
+ }
+ }
+ projectReferences: []
+ - name: "StartUp"
+ rootNamespace: ""
+ sourceFiles: []
+ projectReferences: []
+
+steps:
+ - pwsh: | # Generate shim manifest
+ $outputPath = '${{ parameters.OutputPath }}'
+ $outputDirectory = Split-Path -Path $outputPath -Parent
+ if (-not [string]::IsNullOrWhiteSpace($outputDirectory)) {
+ $null = New-Item -ItemType Directory -Path $outputDirectory -Force
+ }
+
+ $shimDefinitions = $env:SHIMS_JSON | ConvertFrom-Json
+ $projects = [ordered]@{}
+ foreach ($shim in @($shimDefinitions)) {
+ $project = [ordered]@{}
+
+ $sourceFiles = [System.Collections.Generic.List[object]]::new()
+ foreach ($sourceFile in @($shim.sourceFiles)) {
+ if ($null -eq $sourceFile) {
+ continue
+ }
+
+ $sourcePath = [string]$sourceFile.path
+ $sourceContent = [string]$sourceFile.content
+ if ([string]::IsNullOrWhiteSpace($sourcePath)) {
+ continue
+ }
+
+ $null = $sourceFiles.Add([ordered]@{
+ Path = $sourcePath
+ Content = $sourceContent
+ })
+ }
+
+ if ($sourceFiles.Count -gt 0) {
+ $project['SourceFiles'] = @($sourceFiles)
+ }
+
+ $projectReferences = @($shim.projectReferences | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) })
+ if ($projectReferences.Count -gt 0) {
+ $project['ProjectReferences'] = @($projectReferences | ForEach-Object { [string]$_ })
+ }
+
+ $rootNamespace = [string]$shim.rootNamespace
+ if (-not [string]::IsNullOrWhiteSpace($rootNamespace)) {
+ $project['RootNamespace'] = $rootNamespace
+ }
+
+ $projects[[string]$shim.name] = $project
+ }
+
+ $manifest = @{
+ Projects = $projects
+ }
+
+ $manifest | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $outputPath -Encoding utf8
+ Write-Host "Generated shim manifest: $outputPath"
+ displayName: Generate shim manifest
+ env:
+ SHIMS_JSON: ${{ convertToJson(parameters.Shims) }}
diff --git a/templates/FlexibleExplorerAddInTemplate/FlexibleExplorerAddInTemplate.cs b/templates/FlexibleExplorerAddInTemplate/FlexibleExplorerAddInTemplate.cs
index a4f350a..b100b17 100644
--- a/templates/FlexibleExplorerAddInTemplate/FlexibleExplorerAddInTemplate.cs
+++ b/templates/FlexibleExplorerAddInTemplate/FlexibleExplorerAddInTemplate.cs
@@ -2,6 +2,7 @@
using Aveva.ApplicationFramework;
using Aveva.ApplicationFramework.Presentation;
using Aveva.Core.Database;
+using Aveva.Core.Presentation;
namespace Aveva.Core.Samples
{
diff --git a/templates/FlexibleExplorerAddInTemplate/MyFlexibleExplorerControl.cs b/templates/FlexibleExplorerAddInTemplate/MyFlexibleExplorerControl.cs
index 6124ce1..f491d80 100644
--- a/templates/FlexibleExplorerAddInTemplate/MyFlexibleExplorerControl.cs
+++ b/templates/FlexibleExplorerAddInTemplate/MyFlexibleExplorerControl.cs
@@ -17,7 +17,7 @@ public partial class MyFlexibleExplorerControl : UserControl
///
/// The flexible explorer.
///
- private FlexibleExplorer.FlexibleExplorer _flexibleExplorer;
+ private global::Aveva.Core.FlexibleExplorer.FlexibleExplorer _flexibleExplorer;
///
/// The dependency resolver.
@@ -134,7 +134,7 @@ private string ValidatePath(string filePath, string baseDirectory)
///
/// A FlexibleExplorer.
///
- private FlexibleExplorer.FlexibleExplorer InitialiseFlexibleExplorer(string xmlFileName)
+ private global::Aveva.Core.FlexibleExplorer.FlexibleExplorer InitialiseFlexibleExplorer(string xmlFileName)
{
if (_flexibleExplorer != null)
{
@@ -142,7 +142,7 @@ private FlexibleExplorer.FlexibleExplorer InitialiseFlexibleExplorer(string xmlF
}
else
{
- _flexibleExplorer = new FlexibleExplorer.FlexibleExplorer(_dependencyResolver, null, true);
+ _flexibleExplorer = new global::Aveva.Core.FlexibleExplorer.FlexibleExplorer(_dependencyResolver, null, true);
_flexibleExplorer.Dock = DockStyle.Fill;
_flexibleExplorer.BorderStyle = BorderStyle.FixedSingle;
@@ -185,37 +185,6 @@ private FlexibleExplorer.FlexibleExplorer InitialiseFlexibleExplorer(string xmlF
_flexibleExplorer.CreateRoots(definitionProvider.RootDefinitions, false);
}
catch (SecurityException ex)
- {
- // Handle path traversal attempts
- Trace.TraceWarning("[SECURITY] Security exception loading XML '{0}': {1}", xmlFileName, ex.Message);
- Log.Logger().MessageFormat("Security error loading {0}: {1}", xmlFileName, ex.Message);
- ApplicationFramework.Presentation.MessageBoxEx.Show(
- $"Security policy violation: {ex.Message}",
- "Security Error",
- MessageBoxButtons.OK,
- MessageBoxIcon.Error);
- }
- catch (XmlException ex)
- {
- // Handle XML parsing errors
- Log.Logger().MessageFormat("XML parsing error in {0}: {1}", xmlFileName, ex.Message);
- ApplicationFramework.Presentation.MessageBoxEx.Show(
- $"Failed to load XML configuration: {ex.Message}",
- "Configuration Error",
- MessageBoxButtons.OK,
- MessageBoxIcon.Error);
- }
- catch (FileNotFoundException ex)
- {
- // Handle missing file errors
- Log.Logger().MessageFormat("File not found: {0}", ex.Message);
- ApplicationFramework.Presentation.MessageBoxEx.Show(
- $"Configuration file not found: {ex.Message}",
- "File Not Found",
- MessageBoxButtons.OK,
- MessageBoxIcon.Error);
- }
- catch (SecurityException ex)
{
// Security-related initialization failure
Trace.TraceWarning("[SECURITY] Security exception during Flexible Explorer initialization: {0}", ex.GetType().Name);
diff --git a/templates/WebpageAddInTemplate/WebpageAddInTemplate.csproj b/templates/WebpageAddInTemplate/WebpageAddInTemplate.csproj
index 63fb234..6821ad3 100644
--- a/templates/WebpageAddInTemplate/WebpageAddInTemplate.csproj
+++ b/templates/WebpageAddInTemplate/WebpageAddInTemplate.csproj
@@ -57,6 +57,7 @@
+