Create CI and Ship pipelines (#7)

Add ci and ship pipelines
This commit is contained in:
Carl McCaffrey 2021-07-12 17:55:23 +01:00 коммит произвёл GitHub
Родитель 7f638b02a8
Коммит ae4bc3f921
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 1416 добавлений и 4 удалений

24
GraphicsTools.nuspec Normal file
Просмотреть файл

@ -0,0 +1,24 @@
<?xml version="1.0"?>
<!--
GraphicsTools NuGet package metadata.
Ensure to set the `BasePath` parameter to the built plugin directory. (e.g. BuildPlugin.ps1's 'PackageOutDir' parameter)
-->
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>GraphicsTools-UE4.26.0</id>
<title>Mixed Reality Graphics Tools</title>
<description>Graphics tools and components for developing Mixed Reality applications.</description>
<version>0.2.0</version>
<authors>Microsoft</authors>
<projectUrl>https://github.com/microsoft/MixedReality-GraphicsTools-Unreal</projectUrl>
<releaseNotes>https://github.com/microsoft/MixedReality-GraphicsTools-Unreal/blob/public/0.2.x/Docs/ReleaseNotes.md</releaseNotes>
<license type="expression">MIT</license>
<copyright>Copyright (c) Microsoft Corporation. All rights reserved.</copyright>
<icon>native/Resources/Icon128.png</icon>
<tags>HoloLens2</tags>
</metadata>
<files>
<file src="**/*" target="native" />
</files>
</package>

Просмотреть файл

@ -0,0 +1,27 @@
<?xml version="1.0"?>
<!--
GraphicsToolsExamples NuGet package metadata.
Ensure to set the `BasePath` parameter to the built plugin directory. (e.g. BuildPlugin.ps1's 'PackageOutDir' parameter)
-->
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>GraphicsToolsExamples-UE4.26.0</id>
<title>Mixed Reality Graphics Tools Examples</title>
<description>Examples for the Mixed Reality Graphics Tools plugin.</description>
<version>0.2.0</version>
<authors>Microsoft</authors>
<projectUrl>https://github.com/microsoft/MixedReality-GraphicsTools-Unreal</projectUrl>
<releaseNotes>https://github.com/microsoft/MixedReality-GraphicsTools-Unreal/blob/public/0.2.x/Docs/ReleaseNotes.md</releaseNotes>
<license type="expression">MIT</license>
<copyright>Copyright (c) Microsoft Corporation. All rights reserved.</copyright>
<icon>native/Resources/Icon128.png</icon>
<tags>HoloLens2 Examples</tags>
<dependencies>
<dependency id="GraphicsTools-UE4.26.0" version="[0.2.0]" />
</dependencies>
</metadata>
<files>
<file src="**/*" target="native" />
</files>
</package>

Просмотреть файл

@ -10,6 +10,7 @@
"DocsURL": "https://github.com/microsoft/MixedReality-GraphicsTools-Unreal",
"MarketplaceURL": "",
"SupportURL": "https://github.com/microsoft/MixedReality-GraphicsTools-Unreal",
"EngineVersion": "4.26.0",
"CanContainContent": true,
"IsBetaVersion": true,
"IsExperimentalVersion": false,

Просмотреть файл

@ -12,10 +12,12 @@ void FGTClippingConeComponentVisualizer::DrawVisualization(
{
if (const UGTClippingConeComponent* ClippingCone = Cast<const UGTClippingConeComponent>(Component))
{
const FTransform& Transform = ClippingCone->GetComponentTransform();
FVector HalfHeight = Transform.GetScaledAxis(EAxis::X);
FVector Scale = Transform.GetScale3D() * 0.5f;
const FTransform& Transform = ClippingCone->GetComponentTransform();
FVector HalfHeight = Transform.GetScaledAxis(EAxis::X);
FVector Scale = Transform.GetScale3D() * 0.5f;
DrawWireChoppedCone(PDI, Transform.GetLocation(), Transform.GetUnitAxis(EAxis::Z), Transform.GetUnitAxis(EAxis::Y), Transform.GetUnitAxis(EAxis::X), FColor::White, Scale.Y, Scale.Z, Scale.X, 16, SDPG_World);
DrawWireChoppedCone(
PDI, Transform.GetLocation(), Transform.GetUnitAxis(EAxis::Z), Transform.GetUnitAxis(EAxis::Y), Transform.GetUnitAxis(EAxis::X),
FColor::White, Scale.Y, Scale.Z, Scale.X, 16, SDPG_World);
}
}

Просмотреть файл

@ -10,6 +10,7 @@
"DocsURL": "https://github.com/microsoft/MixedReality-GraphicsTools-Unreal",
"MarketplaceURL": "",
"SupportURL": "https://github.com/microsoft/MixedReality-GraphicsTools-Unreal",
"EngineVersion": "4.26.0",
"CanContainContent": true,
"IsBetaVersion": true,
"IsExperimentalVersion": false,

9
Setup.bat Normal file
Просмотреть файл

@ -0,0 +1,9 @@
@ECHO OFF
rem Copyright (c) Microsoft Corporation.
rem Licensed under the MIT License.
rem This script sets up the repo
pushd %~dp0%
git config --local core.hooksPath Tools/hooks
popd

28
Tools/CI/ci.yml Normal file
Просмотреть файл

@ -0,0 +1,28 @@
trigger:
- main
variables:
- template: settings/common.yml
resources:
repositories:
- repository: build-tools
type: git
endpoint: Analog
name: Analog/mixedrealitytoolkit.build
ref: mru-ue
jobs:
- job:
pool:
name: $(AgentPool)
timeoutInMinutes: 30
workspace:
clean: all
steps:
- checkout: self
clean: true
submodules: true
- checkout: build-tools
- template: templates/common.yml

Просмотреть файл

@ -0,0 +1,11 @@
variables:
AgentPool: UXT-UE
SourceDir: $(Build.SourcesDirectory)/$(Build.Repository.Name)
ToolsDir: $(Build.SourcesDirectory)/mixedrealitytoolkit.build
ClangFormat.Version: 11.0.0
GraphicsToolsProjectDir: $(SourceDir)/GraphicsToolsProject
GraphicsToolsPluginDir: $(GraphicsToolsProjectDir)/Plugins/GraphicsTools
GraphicsToolsBuildDir: $(Build.ArtifactStagingDirectory)/GraphicsTools
GraphicsToolsExamplesPluginDir: $(GraphicsToolsProjectDir)/Plugins/GraphicsToolsExamples
GraphicsToolsExamplesBuildDir: $(Build.ArtifactStagingDirectory)/GraphicsToolsExamples
NugetStagingDir: $(Build.ArtifactStagingDirectory)/Nuget

178
Tools/CI/ship.yml Normal file
Просмотреть файл

@ -0,0 +1,178 @@
# Manually triggered pipeline to generate shipping builds of the CI artifacts
trigger: none
variables:
- template: settings/common.yml
resources:
repositories:
- repository: build-tools
type: git
endpoint: Analog
name: Analog/mixedrealitytoolkit.build
ref: mru-ue
jobs:
- job:
pool:
name: $(AgentPool)
timeoutInMinutes: 30
workspace:
clean: all
steps:
- checkout: self
clean: true
submodules: true
- checkout: build-tools
- template: templates/common.yml
parameters:
Configuration: 'Shipping'
- task: ComponentGovernanceComponentDetection@0
displayName: 'Component Governance'
inputs:
scanType: 'Register'
alertWarningLevel: 'High'
failOnAlert: true
- task: NuGetToolInstaller@1
displayName: 'Install NuGet'
inputs:
versionSpec: 5.9.1
- task: EsrpCodeSigning@1
displayName: 'Sign GraphicsTools binaries'
inputs:
ConnectedServiceName: 'MixedReality-GraphicsTools-Unreal-ESRP'
FolderPath: '$(GraphicsToolsBuildDir)'
Pattern: '**/*.dll'
UseMinimatch: true
signConfigType: 'inlineSignParams'
inlineOperation: |
[
{
"KeyCode" : "CP-230012",
"OperationCode" : "SigntoolSign",
"Parameters" : {
"OpusName" : "Microsoft",
"OpusInfo" : "http://www.microsoft.com",
"FileDigest" : "/fd \"SHA256\"",
"PageHash" : "/NPH",
"TimeStamp" : "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
},
"ToolName" : "sign",
"ToolVersion" : "1.0"
},
{
"KeyCode" : "CP-230012",
"OperationCode" : "SigntoolVerify",
"Parameters" : {},
"ToolName" : "sign",
"ToolVersion" : "1.0"
}
]
SessionTimeout: '60'
MaxConcurrency: '50'
MaxRetryAttempts: '5'
- task: EsrpCodeSigning@1
displayName: 'Sign GraphicsToolsExamples binaries'
inputs:
ConnectedServiceName: 'MixedReality-GraphicsTools-Unreal-ESRP'
FolderPath: '$(GraphicsToolsExamplesBuildDir)'
Pattern: '**/*.dll'
UseMinimatch: true
signConfigType: 'inlineSignParams'
inlineOperation: |
[
{
"KeyCode" : "CP-230012",
"OperationCode" : "SigntoolSign",
"Parameters" : {
"OpusName" : "Microsoft",
"OpusInfo" : "http://www.microsoft.com",
"FileDigest" : "/fd \"SHA256\"",
"PageHash" : "/NPH",
"TimeStamp" : "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
},
"ToolName" : "sign",
"ToolVersion" : "1.0"
},
{
"KeyCode" : "CP-230012",
"OperationCode" : "SigntoolVerify",
"Parameters" : {},
"ToolName" : "sign",
"ToolVersion" : "1.0"
}
]
SessionTimeout: '60'
MaxConcurrency: '50'
MaxRetryAttempts: '5'
# The ESRP CodeSign task drops a summary of it's results in the plugin's output directory.
# These should not be included in the NuGet packages so we move them to the $(NugetStagingDir) to keep them available in the build artifact.
# $(NugetStagingDir) has not been created at this stage so we have to create it manually before calling Move-Item.
- powershell: |
New-Item -Path $(NugetStagingDir) -ItemType Directory
Get-ChildItem -Path $(GraphicsToolsBuildDir)/CodeSignSummary-*.md | Move-Item -Destination $(NugetStagingDir)
Get-ChildItem -Path $(GraphicsToolsExamplesBuildDir)/CodeSignSummary-*.md | Move-Item -Destination $(NugetStagingDir)
displayName: 'Move signing results out of NuGet package contents'
- task: NuGetCommand@2
displayName: 'Create GraphicsTools NuGet package'
inputs:
command: 'pack'
packagesToPack: '$(SourceDir)/GraphicsTools.nuspec'
packDestination: '$(NugetStagingDir)'
basePath: '$(GraphicsToolsBuildDir)'
- task: NuGetCommand@2
displayName: 'Create GraphicsToolsExamples NuGet package'
inputs:
command: 'pack'
packagesToPack: '$(SourceDir)/GraphicsToolsExamples.nuspec'
packDestination: '$(NugetStagingDir)'
basePath: '$(GraphicsToolsExamplesBuildDir)'
- task: EsrpCodeSigning@1
displayName: 'Sign NuGet packages'
inputs:
ConnectedServiceName: 'MixedReality-GraphicsTools-Unreal-ESRP'
FolderPath: '$(NugetStagingDir)'
Pattern: '*.nupkg'
UseMinimatch: true
signConfigType: 'inlineSignParams'
inlineOperation: |
[
{
"KeyCode" : "CP-401405",
"OperationCode" : "NuGetSign",
"Parameters" : {},
"ToolName" : "sign",
"ToolVersion" : "1.0"
},
{
"KeyCode" : "CP-401405",
"OperationCode" : "NuGetVerify",
"Parameters" : {},
"ToolName" : "sign",
"ToolVersion" : "1.0"
}
]
SessionTimeout: '60'
MaxConcurrency: '50'
MaxRetryAttempts: '5'
- task: PowerShell@2
displayName: 'Verify code signing'
inputs:
filePath: $(SourceDir)\Tools\scripts\SignCheck.ps1
arguments: -packageDir $(NuGetStagingDir)
- task: PublishBuildArtifacts@1
displayName: 'Publish NuGet packages build artifacts'
inputs:
ArtifactName: 'NuGet Packages'
PathtoPublish: '$(NugetStagingDir)'

Просмотреть файл

@ -0,0 +1,91 @@
parameters:
Configuration: 'Development'
steps:
- task: PowerShell@2
displayName: "Verify code formatting"
inputs:
targetType: 'filePath'
filePath: $(SourceDir)/Tools/scripts/FormatSource.ps1
arguments: -ModifiedOnly $False -UseVS2019 $True -Verify $True
- task: PowerShell@2
displayName: 'Install UnrealEngine'
inputs:
targetType: 'filePath'
filePath: $(ToolsDir)/scripts/install_package.ps1
arguments: -feedName ue4-internal -versionFile $(SourceDir)/EditorVersion.json -destination $(Agent.ToolsDirectory)/ue4 -artifactTool $(Agent.WorkFolder)/ArtifactTool_win10-x64_0.2.128/artifacttool.exe -pipelineVariableName UnrealEngine -PAT $(UE4FeedPAT) -RemovePackagesOlderThanDays 14 -IgnoreCleanUpErrors $false
- task: PowerShell@2
displayName: 'Build GraphicsToolsProject (Win64|${{ parameters.Configuration }})'
inputs:
filePath: $(SourceDir)/Tools/scripts/BuildProject.ps1
arguments: -UnrealEngine $(UnrealEngine) -Platform Win64 -Configuration ${{ parameters.Configuration }} -Clean $True -UnityBuild $False
- task: DownloadSecureFile@1
displayName: 'Download signing certificate'
name: SigningCertificate
inputs:
secureFile: 'SigningCertificate.pfx'
- script: |
IF NOT EXIST "$(GraphicsToolsProjectDir)/Build" mkdir "$(GraphicsToolsProjectDir)/Build"
IF NOT EXIST "$(GraphicsToolsProjectDir)/Build/HoloLens" mkdir "$(GraphicsToolsProjectDir)/Build/HoloLens"
copy "$(SigningCertificate.secureFilePath)" "$(GraphicsToolsProjectDir)/Build/HoloLens/SigningCertificate.pfx"
displayName: 'Copy self-signing certificate (HoloLens)'
- task: PowerShell@2
displayName: 'Build GraphicsToolsProject (HoloLens|${{ parameters.Configuration }})'
inputs:
filePath: $(SourceDir)/Tools/scripts/BuildProject.ps1
arguments: -UnrealEngine $(UnrealEngine) -Platform HoloLens -Configuration ${{ parameters.Configuration }} -Clean $True -UnityBuild $False -ArchivePath $(Build.ArtifactStagingDirectory)/packages
# robocopy returning 0 or 1 indicates success so set the return code to 0, otherwise preserve robocopy's return code.
- script: |
(robocopy "$(GraphicsToolsProjectDir)/Binaries/HoloLens" "$(Build.ArtifactStagingDirectory)/packages/HoloLens" "*.pdb" /s) ^& IF %ERRORLEVEL% LEQ 1 exit 0
displayName: 'Copy HoloLens symbols to artifact'
- task: PublishBuildArtifacts@1
displayName: 'Publish GraphicsToolsProject (HoloLens) to build artifacts'
inputs:
ArtifactName: 'GraphicsToolsProject-HoloLens'
PathtoPublish: '$(Build.ArtifactStagingDirectory)/packages/HoloLens'
condition: and(succeeded(), not(eq(variables['Build.Reason'], 'PullRequest')))
- task: PowerShell@2
displayName: 'Build GraphicsTools'
inputs:
filePath: $(SourceDir)/Tools/scripts/BuildPlugin.ps1
arguments: -UnrealEngine $(UnrealEngine) -PluginDir $(GraphicsToolsPluginDir)/GraphicsTools.uplugin -PackageOutDir $(GraphicsToolsBuildDir)
- task: PublishBuildArtifacts@1
displayName: 'Publish GraphicsTools to build artifacts'
inputs:
ArtifactName: 'GraphicsTools'
PathtoPublish: '$(GraphicsToolsBuildDir)'
condition: and(succeeded(), not(eq(variables['Build.Reason'], 'PullRequest')))
- task: PowerShell@2
displayName: 'Install GraphicsTools into the engine'
inputs:
filePath: $(SourceDir)/Tools/scripts/InstallPlugin.ps1
arguments: -UnrealEngine $(UnrealEngine) -PluginPath $(GraphicsToolsBuildDir)
- task: PowerShell@2
displayName: 'Build GraphicsToolsExamples'
inputs:
filePath: $(SourceDir)/Tools/scripts/BuildPlugin.ps1
arguments: -UnrealEngine $(UnrealEngine) -PluginDir $(GraphicsToolsExamplesPluginDir)/GraphicsToolsExamples.uplugin -PackageOutDir $(GraphicsToolsExamplesBuildDir)
- task: PowerShell@2
displayName: 'Uninstall GraphicsTools from the engine'
inputs:
filePath: $(SourceDir)/Tools/scripts/UninstallPlugin.ps1
arguments: -UnrealEngine $(UnrealEngine) -PluginName GraphicsTools
- task: PublishBuildArtifacts@1
displayName: 'Publish GraphicsToolsExamples to build artifacts'
inputs:
ArtifactName: 'GraphicsToolsExamples'
PathtoPublish: '$(GraphicsToolsExamplesBuildDir)'
condition: and(succeeded(), not(eq(variables['Build.Reason'], 'PullRequest')))

8
Tools/hooks/pre-commit Normal file
Просмотреть файл

@ -0,0 +1,8 @@
#!/bin/sh
# run only on Windows
if [[ "$OSTYPE" == "msys" ]]; then
exec powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ".\Tools\scripts\FormatSource.ps1" -Staged \$true -Verify \$true -UseVS2019 \$true -NoFail \$true
fi
exit 0

Просмотреть файл

@ -0,0 +1,62 @@
<#
.SYNOPSIS
Build Unreal engine plugin.
.PARAMETER UnrealEngine
Path to Unreal Engine (root folder).
.PARAMETER PluginPath
Path to the .uplugin file to build.
.PARAMETER PackageOutDir
Path to where packaged plugin should be published.
#>
param(
[Parameter(Mandatory=$true)]
[string]$UnrealEngine,
[Parameter(Mandatory=$true)]
[string]$PluginPath,
[Parameter(Mandatory=$true)]
[string]$PackageOutDir,
[string]$TargetPlatforms = "Win64+HoloLens+Android",
[boolean]$Clean = $false,
[boolean]$StrictMode = $false,
[boolean]$UseVS2019 = $false
)
Import-Module -Name "$PSScriptRoot\BuildTools.psm1" -Force
$CommandArgs = "BuildPlugin", `
"-Plugin=`"$PluginPath`"", `
"-Package=`"$PackageOutDir`"", `
"-TargetPlatforms=`"$TargetPlatforms`""
if ($Clean)
{
$CommandArgs += "-Clean"
}
if ($StrictMode)
{
$CommandArgs += "-StrictIncludes"
}
if($UseVS2019)
{
$CommandArgs += "-vs2019"
}
# Static Analyzer turned off for Plugin Build as it reports errors in Engine when building Shipping config
$Result = Start-UAT -UnrealEngine $UnrealEngine -CommandArgs $CommandArgs -UseStaticAnalyzer $False -UseUnityBuild (-not $StrictMode) -ErrorAction Stop
$RC = 0
if ($Result)
{
Write-Host "Build successful."
}
else
{
$RC = 1
Write-Host "Build failed. (See log for details)"
}
exit $RC

Просмотреть файл

@ -0,0 +1,87 @@
<#
.SYNOPSIS
Build an UnrealEngine project.
.PARAMETER UnrealEngine
Path to Unreal Engine (root folder).
.PARAMETER Platform
Target platform: Win64, HoloLens, Android
.PARAMETER Configuration
Build Configuration: Debug, Development, Shipping.
.PARAMETER ArchivePath
Optional: path to where the game package should be published.
#>
param(
[Parameter(Mandatory=$true)]
[string]$UnrealEngine,
[Parameter(Mandatory=$true)]
[string]$Platform,
[Parameter(Mandatory=$true)]
[string]$Configuration,
[string]$CookFlavor = $null,
[string]$ProjectPath = $null,
[boolean]$UnityBuild = $true,
[boolean]$Clean = $false,
[string]$ArchivePath = $null,
[boolean]$UseStaticAnalyzer = $false
)
Import-Module -Name "$PSScriptRoot\BuildTools.psm1" -Force
if ([String]::IsNullOrEmpty($ProjectPath))
{
$ProjectPath = Resolve-Path -Path "$PSScriptRoot\..\..\GraphicsToolsProject\GraphicsToolsProject.uproject"
}
$BuildTarget = "GraphicsToolsProject"
$CommandArgs = "BuildCookRun", `
"-project=`"$ProjectPath`"", `
"-cook", `
"-allmaps", `
"-build", `
"-stage", `
"-package", `
"-platform=$Platform", `
"-clientconfig=$Configuration", `
"-serverconfig=$Configuration", `
"-target=$BuildTarget"
if ($Clean -eq $true)
{
$CommandArgs += "-clean"
}
if (-not [String]::IsNullOrEmpty($ArchivePath))
{
$CommandArgs += "-pak"
$CommandArgs += "-archive"
$CommandArgs += "-archivedirectory=`"$ArchivePath`""
}
if (-not [String]::IsNullOrEmpty($CookFlavor))
{
$CommandArgs += "-cookflavor=$CookFlavor"
}
if (-not $UnityBuild)
{
$CommandArgs += "-DisableUnity"
}
$Result = Start-UAT -UnrealEngine $UnrealEngine -CommandArgs $CommandArgs -UseStaticAnalyzer $UseStaticAnalyzer -UseUnityBuild $UnityBuild -ErrorAction Stop
$RC = 0
if ($Result)
{
Write-Host "Build successful."
}
else
{
$RC = 1
Write-Host "Build failed."
}
exit $RC

Просмотреть файл

@ -0,0 +1,364 @@
<#
.SYNOPSIS
Utility functions for building of Unreal projects.
#>
<#
.SYNOPSIS
Create copies of files (if those files already exist).
.PARAMETER Files
List of paths.
.OUTPUTS
{string:string} - dictionary with new paths as keys, old paths as values.
#>
function Backup-Files
{
[CmdletBinding()]
param (
[string[]]$Files
)
process
{
$Result = @{}
$UniqueTimestamp = (Get-Date).Ticks
$Files | ForEach-Object {
if (Test-Path -Path $_ -PathType Leaf)
{
$NewFileName = "$($_).$UniqueTimestamp"
$Result[$NewFileName] = $_
Write-Host "Back up $_ => $NewFileName"
Rename-Item -Path $_ -NewName $NewFileName -Force -ErrorAction Stop
}
}
return $Result
}
}
<#
.SYNOPSIS
Restore files previously backed up with Backup-Files.
.PARAMETER Files
{string:string} - dictionary with new paths as keys, old paths as values.
#>
function Restore-BackupFiles
{
[CmdletBinding()]
param (
$FilePathDictionary
)
process
{
Write-Host "Restoring backed up files."
$Errors = $False
$FilePathDictionary.keys | ForEach-Object {
$BackupFileName = $_
$OriginalFilename = $FilePathDictionary[$BackupFileName]
try
{
Write-Host "Restoring $BackupFileName => $OriginalFilename"
Remove-Item $OriginalFilename -Force
Rename-Item -Path $BackupFileName -NewName $OriginalFilename -Force
}
catch
{
$Errors = $True
Write-Host -ForegroundColor Red "Could not restore file: $OriginalFilename"
Write-Host -ForegroundColor Red " from backup file: $BackupFileName"
Write-Host $_ # exception trace
}
}
if ($Errors)
{
throw "Not all backed up files were restored."
}
}
}
<#
.SYNOPSIS
Create Unreal Engine BuildConfiguration.xml file.
#>
function New-BuildConfiguration
{
[CmdletBinding()]
param (
[string]$OutPath,
[boolean]$UseStaticAnalyzer = $True,
[boolean]$UseUnityBuild = $True
)
process
{
Write-Host "Writing new Unreal Engine Build Configuration to: $OutPath"
$Lines = @()
$Lines += "<?xml version=`"1.0`" encoding=`"utf-8`" ?>"
$Lines += "<Configuration xmlns=`"https://www.unrealengine.com/BuildConfiguration`">"
$Lines += " <BuildConfiguration>"
$Lines += " <bUseUnityBuild>" + "$UseUnityBuild".ToLower() + "</bUseUnityBuild>"
$Lines += " </BuildConfiguration>"
$Lines += " <WindowsPlatform>"
if ($UseStaticAnalyzer)
{
Write-Host " StaticAnalyzer: VisualCpp"
$Lines += " <StaticAnalyzer>VisualCpp</StaticAnalyzer>"
}
$Lines += " </WindowsPlatform>"
$Lines += "</Configuration>"
$Lines -join "`r`n" | Out-File -FilePath $OutPath -Encoding ascii
}
}
function Get-UnrealBuildLogPath
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]$UnrealEngine
)
process
{
$EngineId = ($UnrealEngine -replace "\\","+" -replace ":","").TrimEnd("+")
return "$($Env:APPDATA)\Unreal Engine\AutomationTool\Logs\$EngineId\Log.txt"
}
}
# Example match:
# ProcessResult.StdOut: R:/MixedReali.../GraphicsTools/file.cpp(33): warning C4996: Warning message.
# Groups: 1 = file path; 2 = line [,column]; 3 = error/warning; 4 = code; 5 = message
$BUILD_EVENT_REGEX_MSBUILD = "([\w\d:/\\_\-\.]+)\(([\d,]+)\)\s*: (warning|error) ([A-Z\d]+){0,1}: (.+)"
# R:\MixedReali...\GraphicsToolsProject\file.cpp(1): error: Expected file.h to be first header included.
# Groups: 1 = file path; 2 = line [,column]; 3 = error/warning; 4 = message
$BUILD_EVENT_REGEX_UNREAL = "([\w\d:/\\_\-\.]+)\(([\d,]+)\): (warning|error): (.+)"
$BUILD_EVENT_REGEX_GENERAL = "(WARNING|ERROR): (.+)"
<#
.SYNOPSIS
Parse a line of output and report error/warning message to ADO pipeline.
.OUTPUTS
[boolean] true if no error found (success)
#>
function Format-BuildError {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[AllowEmptyString()]
[string]$Line,
[boolean]$WarningsAsErrors = $True
)
process
{
if ([string]::IsNullOrEmpty($Line))
{
return $True
}
$IssueType = "warning"
if ($Line -cmatch $BUILD_EVENT_REGEX_MSBUILD)
{
$SourcePath = $Matches[1]
$LineNumber = $Matches[2]
$IssueType = $Matches[3]
if ($WarningsAsErrors)
{
$IssueType = "error"
}
$Code = $Matches[4]
$Message = $Matches[5]
$Column = 1
if ($LineNumber -contains ",")
{
$Line, $Column = $LineNumber -split ","
}
$Location = "linenumber=$LineNumber;columnnumber=$Column"
Write-Host "##vso[task.logissue type=$IssueType;sourcepath=$SourcePath;$Location;code=$Code;]$Message"
}
elseif ($Line -cmatch $BUILD_EVENT_REGEX_UNREAL)
{
$SourcePath = $Matches[1]
$LineNumber = $Matches[2]
$IssueType = $Matches[3]
if ($WarningsAsErrors)
{
$IssueType = "error"
}
$Message = $Matches[4]
$Column = 1
if ($LineNumber -contains ",")
{
$LineNumber, $Column = $LineNumber -split ","
}
$Location = "linenumber=$LineNumber;columnnumber=$Column"
Write-Host "##vso[task.logissue type=$IssueType;sourcepath=$SourcePath;$Location;]$Message"
}
elseif ($Line -cmatch $BUILD_EVENT_REGEX_GENERAL)
{
$IssueType = $Matches[1].ToLower()
if ($WarningsAsErrors)
{
$IssueType = "error"
}
$Message = $Matches[2]
Write-Host "##vso[task.logissue type=$IssueType;]$Message"
}
return ($IssueType -ne "error")
}
}
<#
.SYNOPSIS
Parse build log for errors and warnings.
.OUTPUTS
[boolean] $True is no erros found.
#>
function Read-UnrealBuildLog {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]$UnrealEngine,
[boolean]$WarningsAsErrors = $True
)
process
{
$Success = $True
$LogFilePath = (Get-UnrealBuildLogPath -UnrealEngine $UnrealEngine)
if (-not (Test-Path -Path $LogFilePath -PathType Leaf))
{
throw "UAT log not found: $LogFilePath"
}
Write-Host "Reading UAT log: $LogFilePath"
Get-Content -Path $LogFilePath -Encoding UTF8 | ForEach-Object {
$Success = (Format-BuildError -Line $_) -and $Success
}
return $Success
}
}
function Get-UATPath
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]$UnrealEngine
)
process
{
$UE4BatchFilesDir = "$UnrealEngine/Engine/Build/BatchFiles"
$UATPath = "$UE4BatchFilesDir/RunUAT.bat"
if ((-not (Test-Path -Path $UnrealEngine -PathType Container)) -or
(-not (Test-Path -Path $UATPath -PathType Leaf)))
{
Write-Host -ForegroundColor Red "Incorrect UnrealEngine path provided: $UnrealEngine"
Write-Host -ForegroundColor Red "UnrealEngine parameter should point to the root installation folder of Unreal Engine"
throw "Unreal Engine not found"
}
return $UATPath
}
}
<#
.SYNOPSIS
Build a project using Unreal Automation Tool.
.PARAMETER UnrealEngine
Path to Unreal Engine (root folder).
.PARAMETER CommandArgs
Arguments passed to Unreal Automation Tool.
.OUTPUTS
[boolean] true = success, false = errors occurred
#>
function Start-UAT
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true)]
[string]$UnrealEngine,
[Parameter(Mandatory=$true)]
[string[]]$CommandArgs,
[boolean]$UseStaticAnalyzer = $True,
[boolean]$WarningsAsErrors = $True,
[boolean]$UseUnityBuild = $True
)
process
{
$UATPath = (Get-UATPath -UnrealEngine $UnrealEngine)
# Remove previous build log
$UnrealBuildLogPath = (Get-UnrealBuildLogPath -UnrealEngine $UnrealEngine)
if (Test-Path -Path $UnrealBuildLogPath)
{
Write-Host "Removing existing build log: $UnrealBuildLogPath"
Remove-Item -Path $UnrealBuildLogPath -Force -ErrorAction Stop
}
# Remove BuildConfiguration.xml files
$MyDocuments = [Environment]::GetFolderPath("MyDocuments")
$UserBuildConfigurationPath = "$Env:APPDATA\Unreal Engine\UnrealBuildTool\BuildConfiguration.xml"
$BuildConfigurationFiles = @(
$UserBuildConfigurationPath,
"$MyDocuments\Unreal Engine\UnrealBuildTool\BuildConfiguration.xml"
)
$BuildConfigurationBackup = (Backup-Files -Files $BuildConfigurationFiles -ErrorAction Stop)
# Write BuildConfiguration.xml file
New-BuildConfiguration -OutPath $UserBuildConfigurationPath `
-UseStaticAnalyzer $UseStaticAnalyzer `
-UseUnityBuild $UseUnityBuild `
-ErrorAction Stop
$Arguments = ($CommandArgs -join " ")
Write-Host "Running UAT command:`n`t$UATPath $Arguments"
$ProcInfo = New-Object System.Diagnostics.ProcessStartInfo
$ProcInfo.FileName = $UATPath
$ProcInfo.Arguments = $Arguments
$ProcInfo.UseShellExecute = $False
$ProcInfo.RedirectStandardOutput = $True
$Process = New-Object System.Diagnostics.Process
$Process.StartInfo = $ProcInfo
[Void]$Process.Start()
$Success = $True
$Line = ""
while (1)
{
$Line = $Process.StandardOutput.ReadLine()
if ($null -eq $Line)
{
break
}
else
{
Write-Host $line
$Success = (Format-BuildError -Line $Line) -and $Success
}
}
if ($Success)
{
Write-Host "No errors found in the log."
}
else
{
Write-Host "Errors reported during build (see log)."
}
$BuildResultCode = $Process.ExitCode
$Success = $Success -and ($BuildResultCode -eq 0)
Write-Host "UAT finished with RC=$BuildResultCode"
# Restore BuildConfiguration.xml files
Restore-BackupFiles -FilePathDictionary $BuildConfigurationBackup
return $Success
}
}

46
Tools/scripts/Common.psm1 Normal file
Просмотреть файл

@ -0,0 +1,46 @@
<#
.SYNOPSIS
Given the path to the list of raw git changes, returns an array of
those changes rooted in the git root directory.
.DESCRIPTION
For example, the raw git changes will contain lines like:
Assets/File.cs
This function will return a list of paths that look like (assuming
that RepoRoot is C:\repo):
C:\repo\Assets\File.cs
#>
function GetChangedFiles {
[CmdletBinding()]
param(
[string]$Filename,
[string]$RepoRoot
)
process {
$rawContent = Get-Content -Path $Filename
$processedContent = @()
foreach ($line in $rawContent) {
$joinedPath = Join-Path -Path $RepoRoot -ChildPath $line
$processedContent += $joinedPath
}
$processedContent
}
}
<#
.SYNOPSIS
Returns true if the given file is a markdown document and
false otherwise. Uses the extension of the file, not the actual
content to determine this.
#>
function IsMarkdownFile {
[CmdletBinding()]
param(
[string]$Filename
)
process {
[IO.Path]::GetExtension($Filename).ToLower() -eq ".md"
}
}

Просмотреть файл

@ -0,0 +1,244 @@
<#
.SYNOPSIS
Run clang-format on all source files.
.PARAMETER Path
Path to the directory (or file) to be processed recursively. By default scans the entire repo.
.PARAMETER ClangFormat
Path to clang-format executable, e.g. "C:\Tools\clang-format.exe"
.PARAMETER ModifiedOnly
Scan only files modified in current git checkout.
.PARAMETER Staged
Check only files staged for commit
.PARAMETER ChangesFile
Scan only files listed in provided txt file (one path per line). Paths need to be relative to repo root.
.PARAMETER Verify
Whether to fail if files are not formatted (instead of applying changes).
.PARAMETER UseVS2019
Use ClangFormat provided with Visual Studio 2019. This version will take precedence over -ClangFormat parameter
and PATH variable.
.PARAMETER NoFail`
Do not set RC=1 when errors found, i.e. only report errors in output.
#>
[CmdletBinding()]
param (
[string]$Path = $null,
[string]$ClangFormat = $null,
[boolean]$ModifiedOnly = $True,
[boolean]$Staged = $false,
[string]$ChangesFile = $null,
[boolean]$Verify = $false,
[boolean]$UseVS2019 = $true,
[boolean]$NoFail = $false
)
# Only check source files
$FilePatterns = "\.(h|cpp)$"
$RepoRoot = (Resolve-Path "$PSScriptRoot\..\..")
if ([string]::IsNullOrEmpty($Path))
{
$Path = $RepoRoot
}
$ModifiedFiles = $null
if ($ChangesFile)
{
if (-not (Test-Path -Path $ChangesFile -PathType Leaf))
{
Write-Host -ForegroundColor Red "ChangesFile not found: $ChangesFile"
Write-Host "Checking all source files"
}
else
{
Import-Module "$PSScriptRoot\Common.psm1" -Force
$ModifiedFiles = GetChangedFiles -Filename $ChangesFile -RepoRoot $RepoRoot
if (($null -eq $ModifiedFiles) -or ($ModifiedFiles.Count -eq 0))
{
Write-Host -ForegroundColor Green "No modified files to format."
exit 0
}
}
}
elseif ($ModifiedOnly -or $Staged)
{
$ModifiedFiles = @()
Push-Location -Path $RepoRoot
$Success = $False
try
{
if ($Staged)
{
$Status = (& git diff-index --cached --name-only HEAD)
$Success = ($LASTEXITCODE -eq 0)
$Status | ForEach-Object {
$FullPath = Resolve-Path $_ -ErrorAction SilentlyContinue
$FileName = Split-Path -Leaf -Path $FullPath
if ($FileName -match $FilePatterns)
{
$ModifiedFiles += $FullPath
}
}
}
else
{
$Status = (& git status --porcelain)
$Success = ($LASTEXITCODE -eq 0)
$Status | ForEach-Object {
$FullPath = (Resolve-Path ($_.Trim() -split " ",2)[-1] -ErrorAction SilentlyContinue)
$FileName = Split-Path -Leaf -Path $FullPath
if ($FileName -match $FilePatterns)
{
$ModifiedFiles += $FullPath
}
}
}
}
catch
{
# empty
}
if (-not $Success)
{
Write-Host -ForegroundColor Red "Could not get the list of modified files. Check if git is configured correctly."
exit 1
}
Pop-Location
if (($null -eq $ModifiedFiles) -or ($ModifiedFiles.Count -eq 0))
{
Write-Host -ForegroundColor Green "No modified files to format."
exit 0
}
}
if ($UseVS2019)
{
# Attempt to use clang-format from Visual Studio 2019 if available
$LLVMDirs = @("${Env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Enterprise\VC\Tools\Llvm",
"${Env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Professional\VC\Tools\Llvm",
"${Env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Community\VC\Tools\Llvm")
foreach ($LLVMDir in $LLVMDirs)
{
$VS2019ClangFormatX64 = "$LLVMDir\x64\bin\clang-format.exe"
$VS2019ClangFormatX86 = "$LLVMDir\bin\clang-format.exe"
if (Test-Path -Type Leaf -Path $VS2019ClangFormatX64)
{
$ClangFormat = $VS2019ClangFormatX64
break
}
elseif (Test-Path -Type Leaf -Path $VS2019ClangFormatX86)
{
$ClangFormat = $VS2019ClangFormatX86
break
}
}
}
elseif ([string]::IsNullOrEmpty($ClangFormat) -or (-not (Test-Path -Type Leaf -Path $ClangFormat)))
{
# Check for clang-format in PATH
$ClangFormat = (Get-Command -Name "clang-format.exe" -ErrorAction SilentlyContinue)
}
if ([string]::IsNullOrEmpty($ClangFormat))
{
Write-Host -ForegroundColor Red "clang-format.exe not found. Please install VS2019 or make sure clang-format.exe is in PATH."
exit 1
}
function Format-Directory
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$True)]
[string]$Path,
[Parameter(Mandatory=$True)]
[string]$ClangFormat,
[string]$RepoRoot = $null,
[AllowEmptyString()][string]$FilePatterns = $null,
[string[]]$ModifiedFiles = $null,
[boolean]$Verify = $false
)
process
{
if (-not (Test-Path -Path $Path))
{
Write-Host -ForegroundColor Red "Item not found: $Path"
return $False
}
if ($null -eq $FilePatterns)
{
$FilePatterns = ""
}
$Path = Resolve-Path $Path
$Success = $True
$FilesToFormat = @()
if ((Get-Item -Path $Path) -is [System.IO.DirectoryInfo])
{
Get-ChildItem -Path $Path -File `
| Where-Object { $_ -match $FilePatterns } `
| ForEach-Object {
$FilePath = "$Path\$_"
if (($null -eq $ModifiedFiles) -or ($ModifiedFiles -contains $FilePath))
{
if (!($FilePath -match "Intermediate"))
{
$FilesToFormat += $FilePath
}
}
}
Get-ChildItem -Path $Path -Directory `
| ForEach-Object {
$SubResult = (Format-Directory -Path "$Path\$_" `
-ClangFormat $ClangFormat `
-RepoRoot $RepoRoot `
-FilePatterns $FilePatterns `
-ModifiedFiles $ModifiedFiles `
-Verify $Verify)
$Success = $SubResult -and $Success
}
}
else
{
$FilesToFormat += $Path
}
$FilesToFormat | ForEach-Object {
if ($Verify)
{
Write-Host "[clang-format] Checking formatting: $_"
& $ClangFormat --style=file -Werror --dry-run $_
}
else
{
Write-Host "[clang-format] Formatting $_"
& $ClangFormat --style=file -Werror -i $_
}
$Success = (0 -eq $LASTEXITCODE) -and $Success
}
return $Success
}
}
$Success = (Format-Directory -Path $Path `
-ClangFormat $ClangFormat `
-FilePatterns $FilePatterns `
-ModifiedFiles $ModifiedFiles `
-RepoRoot $RepoRoot `
-Verify $Verify)
if ($Success)
{
Write-Host "Done."
exit 0
}
else
{
Write-Host -ForegroundColor Red "Errors found (see output). Please make sure to resolve all issues before opening a Pull Request."
Write-Host -ForegroundColor Red "Formatting can be applied by running:"
Write-Host -ForegroundColor Red " powershell $PSCommandPath -ModifiedOnly `$False [-Path <path to file or directory>]"
if ($NoFail)
{
exit 0 # do not prevent commit when used in pre-commit hook
}
exit 1
}

Просмотреть файл

@ -0,0 +1,60 @@
<#
.SYNOPSIS
Install a plugin into the engine to allow dependent plugins to be packaged.
The plugin is installed as a marketplace plugin.
.PARAMETER UnrealEngine
Path to Unreal Engine (root folder).
.PARAMETER PluginPath
Path to the packaged plugin to install.
#>
param
(
[Parameter(Mandatory=$true)]
[string]$UnrealEngine,
[Parameter(Mandatory=$true)]
[string]$PluginPath
)
$PluginsPath = "$UnrealEngine\Engine\Plugins"
if ((-not (Test-Path -Path $UnrealEngine -PathType Container)) -or
(-not (Test-Path -Path $PluginsPath -PathType Container)))
{
Write-Host -ForegroundColor Red "Incorrect UnrealEngine path provided: $UnrealEngine"
Write-Host -ForegroundColor Red "UnrealEngine parameter should point to the root installation folder of Unreal Engine"
throw "Unreal Engine not found"
}
$PluginName = Split-Path $PluginPath -Leaf
if ((-not (Test-Path -Path $PluginPath -PathType Container)) -or
(-not (Test-Path -Path "$PluginPath\$PluginName.uplugin" -PathType Leaf)))
{
Write-Host -ForegroundColor Red "Invalid plugin path provided: $PluginPath"
Write-Host -ForegroundColor Red "PluginPath parameter should point to the root folder of the plugin"
throw "Plugin not found"
}
$MarketplacePath = "$PluginsPath\Marketplace"
$InstallPath = "$MarketplacePath\$PluginName"
if (Test-Path -Path $InstallPath -PathType Container)
{
Write-Host "Removing existing version of '$PluginName'..."
Remove-Item -Path $InstallPath -Recurse
}
Write-Host "Installing '$PluginPath' into '$MarketplacePath'..."
Copy-Item -Path $PluginPath -Destination $InstallPath -Recurse
$Result = 0
if ($?)
{
Write-Host "Successfully installed $PluginName."
}
else
{
Write-Host -ForegroundColor Red "Failed to install $PluginName."
$Result = 1
}
exit $Result

117
Tools/scripts/SignCheck.ps1 Normal file
Просмотреть файл

@ -0,0 +1,117 @@
<#
.SYNOPSIS
Utility script to validate that input artifacts to the release pipeline are correctly signed.
.PARAMETER packageDir
Path to folder that contains signed NuGet packages.
.PARAMETER recurse
Check for .nupkg files in subdirectories
#>
param (
[Parameter(Mandatory=$true)][string]$packageDir,
[bool]$recurse = $false,
[string]$tmpDir = ""
)
if ($tmpDir -eq "")
{
$tmpDir = Join-Path $packageDir "extracted_packages"
}
$signtool = 'C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe'
$nugetTool = 'nuget' # This should be in %PATH%
$packages = Get-ChildItem "$packageDir\*.nupkg" -Recurse:$recurse
if ($null -eq $packages)
{
Write-Host "No *.nupkg files found"
exit 1
}
Add-Type -AssemblyName System.IO.Compression.FileSystem
$errors = $false
foreach ($package in $packages)
{
$packageFile = $package.FullName
Write-Host "Checking package $packageFile..."
# Check the .nupkg itself
$outLog = New-Item -ItemType "file" -Path "$tmpDir\out.log" -Force
$errLog = New-Item -ItemType "file" -Path "$tmpDir\err.log" -Force
Start-Process $nugetTool -ArgumentList "verify -Signatures $packageFile" -Wait -RedirectStandardOutput $outLog -RedirectStandardError $errLog -WindowStyle Hidden
if ($null -ne (Get-Content $errLog))
{
Write-Output "Error while checking signature for $packageFile`n$(Get-Content $errLog)"
$errors = $true
}
else
{
Get-Content $outLog | Write-Output
Write-Host -ForegroundColor Green "Successfully checked signature for package $packageFile."
}
# Extract the .nupkg
$newDir = Join-Path $tmpDir $package.BaseName
if (Test-Path $newDir)
{
Write-Host "Temporary directory exists - deleting $newDir"
Remove-Item -Recurse -Force -Path $newDir
}
Write-Host "Extracting package to $newDir..."
# Expand-Archive only takes zip files
$newName = "$($package.FullName).zip"
Copy-Item -Path $package -Destination $newName
# -Force used to handle duplicate files in .zip (a file can be packaged multiple times when matched by multiple filters in nuspec)
# This can be safely ignored and currently should not result in an error.
Expand-Archive -Path $newName -DestinationPath $newDir -Force
Remove-Item $newName -Force # delete the zip file as no longer needed
# Check the .nupkg contains a signature
if (-not (Test-Path "$newDir\.signature.p7s"))
{
Write-Output Get-ChildItem $newDir
Write-Error "No signature found for $($package.BaseName)"
$errors = $true
}
# Check the content of the .nupkg
Write-Host "Checking content of package $packageFile..."
$filesToCheck = Get-ChildItem "$newDir" -Include *.dll,*.winmd -Recurse
Write-Host "Found $($filesToCheck.Count) files to check for signing."
foreach ($file in $filesToCheck)
{
Write-Host "Checking file $file..."
$outLog = New-Item -ItemType "file" -Path "$tmpDir\out.log" -Force
$errLog = New-Item -ItemType "file" -Path "$tmpDir\err.log" -Force
Start-Process $signtool -ArgumentList "verify /pa $file" -Wait -RedirectStandardOutput $outLog -RedirectStandardError $errLog -WindowStyle Hidden
if ($null -ne (Get-Content $errLog))
{
Write-Error "Error while checking signature for $file`n$(Get-Content $errLog)"
$errors = $true
}
else
{
Get-Content $outLog | Write-Output
Write-Host -ForegroundColor Green "Successfully checked signature for file $file."
}
}
}
Remove-Item $tmpDir -Recurse
if ($errors)
{
Write-Host "Finished with errors."
exit 1
}
else
{
Write-Host "All tests successful."
exit 0 # always set the return code, otherwise it is set to the exit code from the command that was ran last
}

Просмотреть файл

@ -0,0 +1,52 @@
<#
.SYNOPSIS
Uninstall a plugin from the engine.
Should only be used to remove plugins installed using `InstallPlugin.ps1`
.PARAMETER UnrealEngine
Path to Unreal Engine (root folder).
.PARAMETER PluginName
The name of the plugin to uninstall.
#>
param
(
[Parameter(Mandatory=$true)]
[string]$UnrealEngine,
[Parameter(Mandatory=$true)]
[string]$PluginName
)
$PluginsPath = "$UnrealEngine\Engine\Plugins"
if ((-not (Test-Path -Path $UnrealEngine -PathType Container)) -or
(-not (Test-Path -Path $PluginsPath -PathType Container)))
{
Write-Host -ForegroundColor Red "Incorrect UnrealEngine path provided: $UnrealEngine"
Write-Host -ForegroundColor Red "UnrealEngine parameter should point to the root installation folder of Unreal Engine"
throw "Unreal Engine not found"
}
$MarketplacePath = "$PluginsPath\Marketplace"
$PluginPath = "$MarketplacePath\$PluginName"
if (-not (Test-Path -Path $PluginPath -PathType Container))
{
Write-Host -ForegroundColor Red "Invalid plugin provided: $UnrealEngine"
Write-Host -ForegroundColor Red "The plugin is not installed."
throw "Plugin not found"
}
Write-Host "Uninstalling $PluginName from '$MarketplacePath'..."
Remove-Item -Path $PluginPath -Recurse
$Result = 0
if ($?)
{
Write-Host "Successfully uninstalled $PluginName."
}
else
{
Write-Host -ForegroundColor Red "Failed to uninstall $PluginName."
$Result = 1
}
exit $Result