Aug
31
2011
Recently, we have had a Gatekeeper client that was experiencing issues when attempting to utilize our Forms module. In essence, every time a form was opened through Gatekeeper, the Microsoft Office installer would launch. If it had happened once and then quit, it wouldn't have been such an issue, but it kept happening every single time a form was opened.
Our forms module stores the documents in the database as BLOBs, and then writes them out to the hard drive to be opened in Microsoft Word using OLE. This isn't ground-breaking development here. This technology has been around for quite a while.
We tried numerous things, but I am only going to document the final approach, which as of this writing, has worked successfully in all cases.
Realizing that the issue had to be permission based - be it file based or registry based - we set off on a search using our old friend google.com. After a few failed searches, we finally came across this article, that discussed the issue with the never ending MSI installer. It seems that this is a recurring issue on Windows based machines, as the article comes from Microsoft. It boils down to the permissions on the registry keys/subkeys, and traditional Windows folders (Windows, Program Files, etc.) being corrupted in some way. Luckily, the article shows how to repair them without re-installing Windows on the machine, using a small utility named SubInACL. After reviewing the information, we decided it was worth a try - and I'm glad we did.
After a few trial runs, it was decided this was the fix we needed - but now we had one other problem to solve. This fix needs to be automated AND the command prompt needs to be ran as administrator, and our users don't have those kinds of permissions.
Naturally, our developer prowess kicked in and we started brainstorming. We could definitely write a 'wrapper' program around the SubInACL application, but how would we be able to elevate it so that it can with the proper credentials?
CreateProcessWithLogonW, that's how.
We could embed credentials inside of our application, and kick off the SubInACL application with those credentials automagically - perfect!
Here's how we did it:
1. We didn't want to use a domain admin account, so the IT staff created a special account for us that would have local administrator rights on the machines.
2. Create a .NET application to use the CreateProcessWithLogonW Windows API command to launch the SubInACL program to perform the registry permission fix.
3. Launch Gatekeeper, open a form, & let the MSI finish (& this time, it finishes successfully and never shows up again!)
Since I'd also like to test the code formatting on our new blog, I'm going to post the contents of that application (minus credentials) so that you can see how we solved this issue:
Imports System.Runtime.InteropServices
Public Class ACLReset
Structure PROCESS_INFORMATION
Public process As IntPtr
Public thread As IntPtr
Public processId As Integer
Public threadId As Integer
End Structure
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
Structure STARTUPINFO
Public cb As Integer
Public reserved As String
Public desktop As String
Public title As String
Public x As Integer
Public y As Integer
Public xSize As Integer
Public ySize As Integer
Public xCountChars As Integer
Public yCountChars As Integer
Public fillAttribute As Integer
Public flags As Integer
Public showWindow As UInt16
Public reserved2 As UInt16
Public reserved3 As Byte
Public stdInput As IntPtr
Public stdOutput As IntPtr
Public stdError As IntPtr
End Structure
<DllImport("advapi32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> _
Public Shared Function CreateProcessWithLogonW(ByVal userName As String, ByVal domain As String, ByVal password As String, ByVal logonFlags As UInt32, ByVal applicationName As String, ByVal commandLine As String, ByVal creationFlags As UInt32, ByVal environment As UInt32, ByVal currentDirectory As String, ByRef startupInfo As STARTUPINFO, ByRef processInformation As PROCESS_INFORMATION) As Boolean
End Function
<DllImport("kernel32.dll", CharSet:=CharSet.Unicode, SetLastError:=True, ExactSpelling:=True)> _
Public Shared Function CloseHandle(ByVal hObject As IntPtr) As Integer
End Function
<DllImport("Kernel32.dll", SetLastError:=True)> _
Public Shared Function WaitForSingleObject(ByVal handle As IntPtr, ByVal milliseconds As UInt32) As UInt32
End Function
<DllImport("Kernel32.dll", SetLastError:=True)> _
Public Shared Function GetStdHandle(ByVal handle As IntPtr) As IntPtr
End Function
<DllImport("kernel32.dll", SetLastError:=True)> _
Public Shared Function GetExitCodeProcess(ByVal process As IntPtr, ByRef exitCode As UInt32) As Boolean
End Function
Public Infinite As System.UInt32 = Convert.ToUInt32(&HFFFFFFF)
Public Startf_UseStdHandles As Int32 = &H100
Public StdOutputHandle As Int32 = -11
Public StdErrorHandle As Int32 = -12
Private Sub bgWorker_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
Dim sInfo As New STARTUPINFO
Dim pInfo As New PROCESS_INFORMATION
Dim userName As String = Nothing
Dim runAsUser As String = "USERNAME"
Dim runAsDomain As String = "DOMAIN"
Dim runAsPassword As String = "PASSWORD"
Dim MyPointer As IntPtr = Marshal.AllocHGlobal(4)
Dim MyErrorPointer As IntPtr = Marshal.AllocHGlobal(4)
Dim exitCode As System.UInt32 = Convert.ToUInt32(123456)
Marshal.WriteInt32(MyErrorPointer, StdErrorHandle)
Marshal.WriteInt32(MyPointer, StdOutputHandle)
userName = Environment.UserName
bgWorker.ReportProgress(10)
sInfo.cb = System.Runtime.InteropServices.Marshal.SizeOf(sInfo)
sInfo.reserved = Nothing
sInfo.flags = sInfo.flags And Startf_UseStdHandles
sInfo.stdOutput = MyPointer
sInfo.stdError = MyErrorPointer
sInfo.showWindow = Convert.ToUInt16(0)
bgWorker.ReportProgress(20)
CreateProcessWithLogonW(runAsUser,
runAsDomain,
runAsPassword,
Convert.ToUInt32(1),
"cmd.exe",
"/C """"c:\Program Files\Windows Resource Kits\Tools\subinacl.exe"""" /subkeyreg HKEY_CURRENT_USER /grant=administrators=f /grant=system=f /grant=restricted=r /grant=" + userName + "=f /setowner=administrators > %temp%\subinacl_output.txt",
Convert.ToUInt32(0),
Convert.ToUInt32(0),
"c:\Program Files\Windows Resource Kits\Tools\",
sInfo,
pInfo)
WaitForSingleObject(pInfo.process, Infinite)
GetExitCodeProcess(pInfo.process, exitCode)
CloseHandle(pInfo.process)
CloseHandle(pInfo.thread)
bgWorker.ReportProgress(30)
CreateProcessWithLogonW(runAsUser,
runAsDomain,
runAsPassword,
Convert.ToUInt32(1),
"cmd.exe",
"/C """"c:\Program Files\Windows Resource Kits\Tools\subinacl.exe"""" /keyreg HKEY_CURRENT_USER /grant=administrators=f /grant=system=f /grant=restricted=r /grant=" + userName + "=f /setowner=administrators >> %temp%\subinacl_output.txt",
Convert.ToUInt32(0),
Convert.ToUInt32(0),
"c:\Program Files\Windows Resource Kits\Tools\",
sInfo,
pInfo)
WaitForSingleObject(pInfo.process, Infinite)
CloseHandle(pInfo.process)
CloseHandle(pInfo.thread)
bgWorker.ReportProgress(40)
CreateProcessWithLogonW(runAsUser,
runAsDomain,
runAsPassword,
Convert.ToUInt32(1),
"cmd.exe",
"/C """"c:\Program Files\Windows Resource Kits\Tools\subinacl.exe"""" /subkeyreg HKEY_LOCAL_MACHINE /grant=administrators=f /grant=system=f /grant=users=r /grant=everyone=r /grant=restricted=r /setowner=administrators >> %temp%\subinacl_output.txt",
Convert.ToUInt32(0),
Convert.ToUInt32(0),
"c:\Program Files\Windows Resource Kits\Tools\",
sInfo,
pInfo)
WaitForSingleObject(pInfo.process, Infinite)
CloseHandle(pInfo.process)
CloseHandle(pInfo.thread)
bgWorker.ReportProgress(50)
CreateProcessWithLogonW(runAsUser,
runAsDomain,
runAsPassword,
Convert.ToUInt32(1),
"cmd.exe",
"/C """"c:\Program Files\Windows Resource Kits\Tools\subinacl.exe"""" /keyreg HKEY_LOCAL_MACHINE /grant=administrators=f /grant=system=f /grant=users=r /grant=everyone=r /grant=restricted=r /setowner=administrators >> %temp%\subinacl_output.txt",
Convert.ToUInt32(0),
Convert.ToUInt32(0),
"c:\Program Files\Windows Resource Kits\Tools\",
sInfo,
pInfo)
WaitForSingleObject(pInfo.process, Infinite)
CloseHandle(pInfo.process)
CloseHandle(pInfo.thread)
bgWorker.ReportProgress(60)
CreateProcessWithLogonW(runAsUser,
runAsDomain,
runAsPassword,
Convert.ToUInt32(1),
"cmd.exe",
"/C """"c:\Program Files\Windows Resource Kits\Tools\subinacl.exe"""" /subkeyreg HKEY_CLASSES_ROOT /grant=administrators=f /grant=system=f /grant=users=r /setowner=administrators >> %temp%\subinacl_output.txt",
Convert.ToUInt32(0),
Convert.ToUInt32(0),
"c:\Program Files\Windows Resource Kits\Tools\",
sInfo,
pInfo)
WaitForSingleObject(pInfo.process, Infinite)
CloseHandle(pInfo.process)
CloseHandle(pInfo.thread)
bgWorker.ReportProgress(70)
CreateProcessWithLogonW(runAsUser,
runAsDomain,
runAsPassword,
Convert.ToUInt32(1),
"cmd.exe",
"/C """"c:\Program Files\Windows Resource Kits\Tools\subinacl.exe"""" /keyreg HKEY_CLASSES_ROOT /grant=administrators=f /grant=system=f /grant=users=r /setowner=administrators >> %temp%\subinacl_output.txt",
Convert.ToUInt32(0),
Convert.ToUInt32(0),
"c:\Program Files\Windows Resource Kits\Tools\",
sInfo,
pInfo)
WaitForSingleObject(pInfo.process, Infinite)
CloseHandle(pInfo.process)
CloseHandle(pInfo.thread)
bgWorker.ReportProgress(80)
CreateProcessWithLogonW(runAsUser,
runAsDomain,
runAsPassword,
Convert.ToUInt32(1),
"cmd.exe",
"/C """"c:\Program Files\Windows Resource Kits\Tools\subinacl.exe"""" /subdirectories %programfiles%\ /grant=administrators=f /grant=system=f /grant=users=e >> %temp%\subinacl_output.txt",
Convert.ToUInt32(0),
Convert.ToUInt32(0),
"c:\Program Files\Windows Resource Kits\Tools\",
sInfo,
pInfo)
WaitForSingleObject(pInfo.process, Infinite)
CloseHandle(pInfo.process)
CloseHandle(pInfo.thread)
bgWorker.ReportProgress(90)
CreateProcessWithLogonW(runAsUser,
runAsDomain,
runAsPassword,
Convert.ToUInt32(1),
"cmd.exe",
"/C """"c:\Program Files\Windows Resource Kits\Tools\subinacl.exe"""" /subdirectories %windir%\ /grant=administrators=f /grant=system=f /grant=users=e >> %temp%\subinacl_output.txt",
Convert.ToUInt32(0),
Convert.ToUInt32(0),
"c:\Program Files\Windows Resource Kits\Tools\",
sInfo,
pInfo)
WaitForSingleObject(pInfo.process, Infinite)
CloseHandle(pInfo.process)
CloseHandle(pInfo.thread)
bgWorker.ReportProgress(100)
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
bgWorker.RunWorkerAsync()
End Sub
Private Sub bgWorker_ProgressChanged(sender As System.Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles bgWorker.ProgressChanged
ProgressBar1.Value = e.ProgressPercentage
End Sub
Private Sub bgWorker_RunWorkerCompleted(sender As System.Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted
Threading.Thread.Sleep(5000)
Application.Exit()
End Sub
End Class
Now, there isn't a lot (if any) error handling going on here, and that's simply because we were in somewhat of a rush to find a solution. At some point, I'll probably revisit this application and refine it a bit.
The code above is simply elevating a command prompt with the provided credentials, and then passing in parameters to that prompt to run the SubInACL utility with the parameters being passed to it that were outlined on the blog post above. It does utilize a background worker, only because I have a progress bar on the interface that gets updated after each command prompt terminates. If you have any more questions about the what or why of the code, please leave a comment!