Header Image

Roman Hergenreder

IT-Security Consultant / Penetration Tester

Microsoft Office Automation

This article explains how to grant specific users permissions to launch Office Applications (in this case: Microsoft Word 2019) via the COM-Interface through Powershell. Additionally, I'll show how to automatically install Microsoft Office and set up an example scheduled task, to launch the application. Last modified: 2022-09-06 07:29:51

Table of Contents

  1. Introduction
  2. Automated Office Installation
  3. Scheduled Task
  4. Permission Setup
Starting a Word or Excel-Application to access it's API from a task which has no GUI, such as a scheduled task or triggered by the IIS, is not easy. Usually, you will have a PowerShell Script calling New-Object with the -ComObject parameter. However, the COM-Interface requires additional permissions which are not granted by default to all users. Trying to run it anyway usually results in the following error message:
PS>$ $Word = New-Object -ComObject "Word.Application"
New-Object : Retrieving the COM class factory for component with CLSID {000209FF-0000-0000-C000-000000000046} failed
due to the following error: 80070005 Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)).
At C:\Users\[user]\Documents\[some_script].ps1:1 char:9
+ $Word = New-Object -ComObject "Word.Application"
The configuration can actually be easily done with the dcomcnfg.exe utility like described in other articles. For Powershell however, there is no built-in interface. Certain registry keys have to be set and, depending on the environment, some privileges have to be granted. Furthermore, when opening the dcomcnfg.exe it does not only set desired permissions but also make some relations between the Class-SID and the App-SID inside the registry. This is important for our script to work.
Office can be installed automated without any need of user interaction of GUI. For this we need two things: a xml-configuration file including the desired office components and the Office Deployment Tool available on Microsoft's official pages. The xml-configuration file can also be easily generated with this tool
Now to automatically process the complete setup, our script only needs to unpack and launch tool:
Start-Process -Wait -FilePath "C:\Path\To\officedeploymenttool.exe" -ArgumentList '/extract:C:\temp\officedeployment','/passive','/quiet','/norestart' -passthru
Start-Process -Wait -FilePath "C:\temp\officedeployment\setup.exe" -ArgumentList '/configure','C:\temp\office_config.xml' -passthru
In my example I wanted to run Word macros from powershell triggered by an scheduled task. Such tasks are especially useful when creating vulnerable machines. The first important thing to mention is, when running scheduled tasks under a specific user, both username and password has to be set. Additionally, the user needs the SeBatchLogonRight privilege. I used the carbon module to grant these privileges. Carbon can be easily installed with choco, which in turn can easily be installed with a powershell one-liner. The complete powershell script to create such a scheduled task looks like this:
$userIdentity = "Computer\SomeUser"
$userPassword = "[Redacted]"

# Install Chocolatey if it's not already available
if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
  IEX((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
}

# install and import carbon
choco install carbon -y --no-progress
Import-Module 'Carbon'

# grant privilege
Grant-CPrivilege -Identity $userIdentity -Privilege 'SeBatchLogonRight'

# create scheduled task, which runs every 3 minutes
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 3)
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-noprofile -ExecutionPolicy Bypass C:\Path\To\Script"
$settings = New-ScheduledTaskSettingsSet -MultipleInstances "Parallel"
Register-ScheduledTask -Action $action -TaskName "[some name]" -User $userIdentity -Password $userPassword -Trigger $trigger -Settings $settings
Consider, we want to run automated Microsoft Word, we first need to find two SIDs: the Class SID and the APP SID. Both values are globally unique (so independent from the system where it's installed), and can be found either in the error logs, where the Access is denied error occurs, in the dcomcnfg.exe utility, or on public SID collections. In my case for Microsoft Office Word 2019, I needed the following SIDs:
Next, we need a tool called DComPerm.exe which should be included in the Windows SDK but also exist as standalone binary hosted on this repository. For the final script, we now need to perform the followings steps:
  1. Register MS Word in the registry (Not sure if this step is required)
  2. Grant Local, Access, Launch and Activate permissions for the CLSID
  3. Grant Local, Access, Launch and Activate permissions for the AppSID
  4. Create CLSID <-> AppSID relation

The equivalent powershell code looks like this:

$dcomPerm = "C:\Some\Path\To\DComPermEx.exe"
$wordAppId = '{00020906-0000-0000-C000-000000000046}'
$wordClassId = '{000209FF-0000-0000-C000-000000000046}'
$userIdentity = 'Computer\SomeUser'
$userPassword = '[REDACTED]'

# Register Word in registry
Start-Process -Wait -FilePath "C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE" -ArgumentList '/r','/q' -passthru

# Application SID
Start-Process -Wait -FilePath $dcomPerm -ArgumentList '-aa',$wordAppId,'set',$userIdentity,'permit','level:l' -passthru
Start-Process -Wait -FilePath $dcomPerm -ArgumentList '-al',$wordAppId,'set',$userIdentity,'permit','level:l' -passthru
Start-Process -Wait -FilePath $dcomPerm -ArgumentList '-runas',$wordAppId,$userIdentity,$userPassword -passthru

# Class SID
Start-Process -Wait -FilePath $dcomPerm -ArgumentList '-aa',$wordClassId,'set',$userIdentity,'permit','level:l' -passthru
Start-Process -Wait -FilePath $dcomPerm -ArgumentList '-al',$wordClassId,'set',$userIdentity,'permit','level:l' -passthru
Start-Process -Wait -FilePath $dcomPerm -ArgumentList '-runas',$wordClassId,$userIdentity,$userPassword -passthru

# App <-> Class Relation
New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT | Out-Null
Set-ItemProperty -Path "HKCR:\CLSID\$($wordClassId)" -Name "AppId" -Value $wordAppId