Skip to content

Commit

Permalink
ExecuteCommand: Launch a deferred command
Browse files Browse the repository at this point in the history
  • Loading branch information
nirbar committed Dec 11, 2023
1 parent 3d736a0 commit e9d57db
Show file tree
Hide file tree
Showing 14 changed files with 464 additions and 15 deletions.
116 changes: 107 additions & 9 deletions PanelSwCustomActions/ExecOnComponent.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "ExecOnComponent.h"
#include "../CaCommon/RegistryKey.h"
#include <Poco/JSON/Parser.h>
#include "FileOperations.h"
#include "../CaCommon/WixString.h"
#include <regex>
Expand All @@ -13,8 +14,10 @@
using namespace std;
using namespace com::panelsw::ca;
using namespace google::protobuf;
using namespace Poco::JSON;
#pragma comment (lib, "shlwapi.lib")
#pragma comment (lib, "Rpcrt4.lib")
#pragma comment (lib, "PocoJsonmt.lib")

enum Flags
{
Expand Down Expand Up @@ -46,6 +49,89 @@ enum Flags
static HRESULT ScheduleExecution(LPCWSTR szId, const CWixString& szCommand, LPCWSTR szWorkingDirectory, LPCWSTR szDomain, LPCWSTR szUser, LPCWSTR szPassword, CExecOnComponent::ExitCodeMap *pExitCodeMap, std::vector<ConsoleOuputRemap> *pConsoleOuput, CExecOnComponent::EnvironmentMap *pEnv, int nFlags, int errorHandling, CExecOnComponent* pBeforeStop, CExecOnComponent* pAfterStop, CExecOnComponent* pBeforeStart, CExecOnComponent* pAfterStart, CExecOnComponent* pBeforeStopImp, CExecOnComponent* pAfterStopImp, CExecOnComponent* pBeforeStartImp, CExecOnComponent* pAfterStartImp);
static const int OUTPUT_BUFFER_SIZE = 1024;

extern "C" UINT __stdcall ExecuteCommand(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
CWixString szExecuteCommand;
LPSTR szExecuteCommandAnsi = nullptr;
Parser parser;
Poco::Dynamic::Var jsonVar;
Object::Ptr jsonObject;
string command, workingFolder, id;
bool isAsync;
CWixString szCommandFormat, szCommand;
CWixString szWorkingFolderFormat, szWorkingFolder;
CWixString szId;
ErrorHandling errorHandling = ErrorHandling::fail;
CExecOnComponent cad;

hr = WcaInitialize(hInstall, __FUNCTION__);
ExitOnFailure(hr, "Failed to initialize");
WcaLog(LOGMSG_STANDARD, "Initialized from PanelSwCustomActions " FullVersion);

hr = WcaGetProperty(L"PSW_ExecuteCommand", (LPWSTR*)szExecuteCommand);
ExitOnFailure(hr, "Failed to get property");

hr = szExecuteCommand.ToAnsiString(&szExecuteCommandAnsi);
ExitOnFailure(hr, "Failed to get ANSI property");

try
{
jsonVar = parser.parse(szExecuteCommandAnsi);
jsonObject = jsonVar.extract<Object::Ptr>();

id = jsonObject->getValue<string>("Id");
command = jsonObject->getValue<string>("Command");
workingFolder = jsonObject->getValue<string>("WorkingFolder");
isAsync = jsonObject->getValue<bool>("Async");
errorHandling = (ErrorHandling)jsonObject->getValue<int>("ErrorHandling");
}
catch (JSONException ex)
{
hr = HRESULT_FROM_WIN32(ex.code());
if (SUCCEEDED(hr))
{
hr = E_FAIL;
}
ExitOnFailure(hr, "Failed to parse json. %hs. %hs", ex.message().c_str(), ex.displayText().c_str())
}
catch (exception ex)
{
hr = E_FAIL;
ExitOnFailure(hr, "Failed to parse json. %hs", ex.what());
}

hr = szId.Format(L"%hs", id.c_str());
ExitOnFailure(hr, "Failed to format ID");

// Format command
hr = szCommandFormat.Format(L"%hs", command.c_str());
ExitOnFailure(hr, "Failed to format command");
hr = szCommand.MsiFormat((LPCWSTR)szCommandFormat);
ExitOnFailure(hr, "Failed to msi-format command");

// Format working folder
if (workingFolder.size())
{
hr = szWorkingFolderFormat.Format(L"%hs", workingFolder.c_str());
ExitOnFailure(hr, "Failed to format working folder");
hr = szWorkingFolder.MsiFormat((LPCWSTR)szWorkingFolderFormat);
ExitOnFailure(hr, "Failed to msi-format working folder");
}

CDeferredActionBase::LogUnformatted(LOGLEVEL::LOGMSG_STANDARD, false, L"Will execute command '%ls'", szCommand.Obfuscated());
hr = cad.AddExec(szCommand, (LPCWSTR)szWorkingFolder, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, isAsync ? Flags::ASync : 0, errorHandling);
ExitOnFailure(hr, "Failed to create command");

hr = cad.DoDeferredAction((LPCWSTR)szId);
ExitOnFailure(hr, "Failed to schedule deferred custom action");

LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}

extern "C" UINT __stdcall ExecOnComponent(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
Expand Down Expand Up @@ -503,8 +589,17 @@ HRESULT CExecOnComponent::AddExec(const CWixString& szCommand, LPCWSTR szWorking
}
pDetails->set_async(nFlags & Flags::ASync);
pDetails->set_errorhandling(errorHandling);
pDetails->mutable_exitcoderemap()->insert(pExitCodeMap->begin(), pExitCodeMap->end());
pDetails->mutable_environment()->insert(pEnv->begin(), pEnv->end());

if (pExitCodeMap)
{
pDetails->mutable_exitcoderemap()->insert(pExitCodeMap->begin(), pExitCodeMap->end());
}

if (pEnv)
{
pDetails->mutable_environment()->insert(pEnv->begin(), pEnv->end());
}

if (szUser && *szUser)
{
pDetails->set_user(szUser, WSTR_BYTE_SIZE(szUser));
Expand All @@ -518,14 +613,17 @@ HRESULT CExecOnComponent::AddExec(const CWixString& szCommand, LPCWSTR szWorking
}
}

for (size_t i = 0; i < pConsoleOuput->size(); ++i)
if (pConsoleOuput)
{
ConsoleOuputRemap* pConsole = pDetails->add_consoleouputremap();
pConsole->mutable_regex()->set_plain(pConsoleOuput->at(i).regex().plain());
pConsole->mutable_regex()->set_obfuscated(pConsoleOuput->at(i).regex().obfuscated());
pConsole->set_prompttext(pConsoleOuput->at(i).prompttext());
pConsole->set_onmatch(pConsoleOuput->at(i).onmatch());
pConsole->set_errorhandling(pConsoleOuput->at(i).errorhandling());
for (size_t i = 0; i < pConsoleOuput->size(); ++i)
{
ConsoleOuputRemap* pConsole = pDetails->add_consoleouputremap();
pConsole->mutable_regex()->set_plain(pConsoleOuput->at(i).regex().plain());
pConsole->mutable_regex()->set_obfuscated(pConsoleOuput->at(i).regex().obfuscated());
pConsole->set_prompttext(pConsoleOuput->at(i).prompttext());
pConsole->set_onmatch(pConsoleOuput->at(i).onmatch());
pConsole->set_errorhandling(pConsoleOuput->at(i).errorhandling());
}
}

pAny = pCmd->mutable_details();
Expand Down
1 change: 1 addition & 0 deletions PanelSwCustomActions/PanelSwCustomActions.def
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ EXPORTS
PromptFileDowngrades
IsWindowsVersionOrGreater
ListProcessorFeatures
ExecuteCommand
2 changes: 1 addition & 1 deletion PanelSwCustomActions/PanelSwCustomActions.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(WixSdkPath)\inc;$(WixSdkPath)\$(WixPlatformToolset)\inc;$(SolutionDir)\CaCommon;$(SolutionDir)\poco\Zip\include;$(SolutionDir)\poco\Foundation\include;..\protobuf\src;$(IntDir)\..\Protobuf;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(WixSdkPath)\inc;$(WixSdkPath)\$(WixPlatformToolset)\inc;$(SolutionDir)\CaCommon;$(SolutionDir)\poco\Zip\include;$(SolutionDir)\poco\Foundation\include;$(SolutionDir)\poco\JSON\include;..\protobuf\src;$(IntDir)\..\Protobuf;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;FullVersion="$(FullVersion)";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
Expand Down
17 changes: 16 additions & 1 deletion PanelSwWixExtension.sln
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PromptFileDowngradesUT", "U
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "IsWindowsVersionOrGreaterUT", "UnitTests\IsWindowsVersionOrGreaterUT\IsWindowsVersionOrGreaterUT.wixproj", "{E4D1FCE8-9273-47DE-A672-209F010F74B7}"
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "ListProcessorFeaturesUT", "UnitTests\ListProcessorFeaturesUT\ListProcessorFeaturesUT.wixproj", "{DE2A15B5-B20E-47C3-87FB-7881D4D7E15A}"
Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "ListProcessorFeaturesUT", "UnitTests\ListProcessorFeaturesUT\ListProcessorFeaturesUT.wixproj", "{DE2A15B5-B20E-47C3-87FB-7881D4D7E15A}"
EndProject
Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "ExecuteCommandUT", "UnitTests\ExecuteCommandUT\ExecuteCommandUT.wixproj", "{A01CBD4F-7229-4B71-8657-7683C98BCB7C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -368,6 +370,18 @@ Global
{DE2A15B5-B20E-47C3-87FB-7881D4D7E15A}.Release|x64.ActiveCfg = Release|x86
{DE2A15B5-B20E-47C3-87FB-7881D4D7E15A}.Release|x86.ActiveCfg = Release|x86
{DE2A15B5-B20E-47C3-87FB-7881D4D7E15A}.Release|x86.Build.0 = Release|x86
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Debug|ARM64.Build.0 = Debug|ARM64
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Debug|x64.ActiveCfg = Debug|x64
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Debug|x64.Build.0 = Debug|x64
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Debug|x86.ActiveCfg = Debug|x86
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Debug|x86.Build.0 = Debug|x86
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Release|ARM64.ActiveCfg = Release|ARM64
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Release|ARM64.Build.0 = Release|ARM64
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Release|x64.ActiveCfg = Release|x64
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Release|x64.Build.0 = Release|x64
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Release|x86.ActiveCfg = Release|x86
{A01CBD4F-7229-4B71-8657-7683C98BCB7C}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -416,6 +430,7 @@ Global
{4A036455-6D14-4783-8F34-B03EE5A04146} = {B533D9FC-4927-4A05-892D-B90E8BEDCF5D}
{E4D1FCE8-9273-47DE-A672-209F010F74B7} = {B533D9FC-4927-4A05-892D-B90E8BEDCF5D}
{DE2A15B5-B20E-47C3-87FB-7881D4D7E15A} = {B533D9FC-4927-4A05-892D-B90E8BEDCF5D}
{A01CBD4F-7229-4B71-8657-7683C98BCB7C} = {B533D9FC-4927-4A05-892D-B90E8BEDCF5D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F5B6389D-77ED-4938-A322-8DFCA4D9FEF5}
Expand Down
148 changes: 148 additions & 0 deletions PanelSwWixExtension/PanelSwWixCompiler.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Microsoft.Tools.WindowsInstallerXml;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;

Expand Down Expand Up @@ -104,6 +106,10 @@ public override void ParseElement(SourceLineNumberCollection sourceLineNumbers,
ParseSetPropertyFromPipe(element);
break;

case "ExecuteCommand":
ParseExecuteCommand(element);
break;

default:
Core.UnexpectedElement(parentElement, element);
break;
Expand Down Expand Up @@ -954,6 +960,148 @@ private void ParseCustomActionDataAttribute(SourceLineNumberCollection sourceLin
}
}

private void ParseExecuteCommand(XmlElement element)
{
SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
string id = "exc" + Guid.NewGuid().ToString("N");
ErrorHandling promptOnError = ErrorHandling.fail;
bool isAsync = false, impersonate = true;
string command = null, workingFolder = "", condition = null, before = null, after = null;
Microsoft.Tools.WindowsInstallerXml.Serialize.CustomAction.ExecuteType executeType = Microsoft.Tools.WindowsInstallerXml.Serialize.CustomAction.ExecuteType.deferred;

foreach (XmlAttribute attrib in element.Attributes)
{
if ((0 != attrib.NamespaceURI.Length) && (attrib.NamespaceURI != schema.TargetNamespace))
{
continue;
}
switch (attrib.LocalName)
{
case "Id":
id = Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
break;
case "ErrorHandling":
{
string a = Core.GetAttributeValue(sourceLineNumbers, attrib);
try
{
promptOnError = (ErrorHandling)Enum.Parse(typeof(ErrorHandling), a);
}
catch
{
Core.UnexpectedAttribute(sourceLineNumbers, attrib);
}
}
break;
case "Wait":
isAsync = (Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No);
break;
case "Impersonate":
impersonate = (Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.Yes);
break;
case "Command":
command = Core.GetAttributeValue(sourceLineNumbers, attrib);
break;
case "WorkingDirectory":
workingFolder = Core.GetAttributeValue(sourceLineNumbers, attrib);
break;
case "Before":
before = Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
break;
case "After":
after = Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
break;
case "Condition":
condition = Core.GetAttributeValue(sourceLineNumbers, attrib);
break;
case "Execute":
string val = Core.GetAttributeValue(sourceLineNumbers, attrib);
if (!Enum.TryParse(val, out executeType))
{
Core.OnMessage(WixErrors.IllegalAttributeValueWithIllegalList(sourceLineNumbers, element.LocalName, attrib.LocalName, val, "deferred, commit, rollback"));
break;
}
switch (executeType)
{
case Microsoft.Tools.WindowsInstallerXml.Serialize.CustomAction.ExecuteType.deferred:
case Microsoft.Tools.WindowsInstallerXml.Serialize.CustomAction.ExecuteType.rollback:
case Microsoft.Tools.WindowsInstallerXml.Serialize.CustomAction.ExecuteType.commit:
break;
default:
Core.OnMessage(WixErrors.IllegalAttributeValueWithIllegalList(sourceLineNumbers, element.LocalName, attrib.LocalName, val, "deferred, commit, rollback"));
break;
}
break;

default:
Core.UnexpectedAttribute(sourceLineNumbers, attrib);
break;
}
}

if (string.IsNullOrEmpty(command))
{
Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, element.LocalName, "Command"));
}
if (string.IsNullOrEmpty(after) == string.IsNullOrEmpty(before))
{
Core.OnMessage(WixErrors.NeedSequenceBeforeOrAfter(sourceLineNumbers, element.LocalName));
}

if (Core.EncounteredError)
{
return;
}

// Create 3 custom actions:
// 1. Set PSW_ExecuteCommand property
// 2. Call PanelSwCustomActions::ExecuteCommand
// 3. PanelSwCustomActions::CommonDeferred

// 1. Set PSW_ExecuteCommand property
Core.CreateWixSimpleReferenceRow(sourceLineNumbers, "Binary", "PanelSwCustomActions.dll");
Core.CreateWixSimpleReferenceRow(sourceLineNumbers, "Property", "PSW_ExecuteCommand");
JObject json = JObject.FromObject(new { Id = id, Command = command, WorkingFolder = workingFolder, Async = isAsync, ErrorHandling = (int)promptOnError });
string cad = json.ToString();
cad = Regex.Replace(cad, @"([\[\]\{\}])", @"[\$1]");

Row prepareCA = Core.CreateRow(sourceLineNumbers, "CustomAction");
prepareCA[0] = $"Prepare{id}";
prepareCA[1] = 0x00000030 | 0x00000003; // Set formatted property
prepareCA[2] = "PSW_ExecuteCommand";
prepareCA[3] = cad;

Row setCadSched = Core.CreateRow(sourceLineNumbers, "WixAction");
setCadSched[0] = "InstallExecuteSequence";
setCadSched[1] = $"Prepare{id}";
setCadSched[2] = condition;
setCadSched[4] = $"Sched{id}"; // beforeAction
setCadSched[5] = null; // afterAction
setCadSched[6] = 0; // not overridable

// 2. Call PanelSwCustomActions::ExecuteCommand
Row schedCAD = Core.CreateRow(sourceLineNumbers, "CustomAction");
schedCAD[0] = $"Sched{id}";
schedCAD[1] = 0x00000001 | 0x00000000; // MsidbCustomActionTypeDll | MsidbCustomActionTypeBinaryData
schedCAD[2] = "PanelSwCustomActions.dll";
schedCAD[3] = "ExecuteCommand";

Row schedCadSched = Core.CreateRow(sourceLineNumbers, "WixAction");
schedCadSched[0] = "InstallExecuteSequence";
schedCadSched[1] = $"Sched{id}";
schedCadSched[2] = condition; // condition
schedCadSched[4] = before; // beforeAction
schedCadSched[5] = after; // afterAction
schedCadSched[6] = 0; // not overridable

// 3. PanelSwCustomActions::CommonDeferred
Row deferredCA = Core.CreateRow(sourceLineNumbers, "CustomAction");
deferredCA[0] = id;
deferredCA[1] = 0x00000001 | 0x00000000 | 0x00002000 | 0x00000400 | (impersonate ? 0 : 0x00000800) | (int)executeType; // MsidbCustomActionTypeDll | MsidbCustomActionTypeBinaryData | MsidbCustomActionTypeHideTarget | MsidbCustomActionTypeInScript | MsidbCustomActionTypeNoImpersonate(?) | deferred/rollback/commit
deferredCA[2] = "PanelSwCustomActions.dll";
deferredCA[3] = "CommonDeferred";
}

private void ParseWebsiteConfigElement(XmlElement element, string component)
{
SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
Expand Down
3 changes: 3 additions & 0 deletions PanelSwWixExtension/PanelSwWixExtension.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.1\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
Expand Down
Loading

0 comments on commit e9d57db

Please sign in to comment.