# Tuesday, September 25, 2007
Last Time

In part 1 of this series I covered generally what I am going to show and also gave some brief information regarding the PE format. At the end I provided some code that would allow a Visual FoxPro developer to step through some of the PE structures and data. In this blog entry I'm going to show you how you can edit the application manifest in a PE File and how that can facilitate elevated execution permissions in Vista UAC and Reg-Free COM.

Application Manifests in Visual FoxPro

Some time ago the Visual FoxPro Team started adding configuration information as a resource to Visual FoxPro DLLs and EXEs. This configuration information is actually an Application Manifest. Let's take a quick look at one. Run the following code and when prompted select one of you VFP EXEs or DLLs.

Declare Long LoadLibrary In WIN32API String
Declare Long FindResource In WIN32API Long, Long, Long
Declare Long LoadResource In WIN32API Long, Long
Declare Long SizeofResource In WIN32API Long, Long
Declare Long FreeLibrary In WIN32API Long
Declare Long FreeResource In WIN32API Long

Local lcModule, hModule
m.lcModule = Getfile("EXE|DLL")
m.hModule = LoadLibrary(m.lcModule)
m.lnRsrc = FindResource(m.hModule, 1, 24)
m.lnMem = LoadResource(m.hModule, m.lnRsrc)
m.lnSize = SizeofResource(m.hModule, m.lnRsrc)
m.lcManifest = Sys(2600, m.lnMem, m.lnSize)
FreeResource(m.lnMem)
FreeLibrary(m.hModule)
?m.lcManifest

You should be seeing something similar to the following...

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
    version="1.0.0.0"
    type="win32"
    name="Microsoft.VisualFoxPro"
    processorArchitecture="x86"
/>
<description>Visual FoxPro</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
language="*"
processorArchitecture="x86"
publicKeyToken="6595b64144ccf1df"
/>
</dependentAssembly>
</dependency>
</assembly>

Visual FoxPro 9.0 SP2

Now, for those of you that selected an EXE or DLL that was built with VFP9 SP2 you will note that your manifest xml is not exactly the same as what I posted above. In VFP9 SP2 the manifest has an additional trustinfo node that is similar to...

<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel
level="asInvoker"
uiAccess="false"/>
</requestedPrivileges>
</security>

This trustinfo node is used for security related stuff, and it effects the way User Account Control on Vista reacts to the start of your application and what it allows your application to do when it is runnning. Note the requestedExecutionLevel node and more specifically the level attribute that it contains. "asInvoker" means to basically run your application with the least priveleges possible (it was actually called leastPrivelege at one time if I remember right). So, unless your users right-click on your application and choose Run as Administrator, or they create a shortcut to your application that tells Vista to always run your application as Administrator, then your application is going to be very limited in what it can do on Vista. That's actually a good thing, but more on that in a moment. For the most part this is viewed as a royal pain in the butt by most and one of the chief complaints that developers and users alike have about Vista.

Can We Change the Application Manifest?

So, surely Microsoft provided Visual FoxPro's Project Info or Project Build dialogs with a way to change this execution level setting, right? Wrong. As of the last SP2 Beta I saw there was nothing for this. Now, that's not necessarily because they weren't looking out for us (although I agree that it would have been nice to have). Probably the reason they didn't do it is because Microsoft has decreed that Certified for Windows Vista requires that applications are marked asInvoker. Should you need to accomplish tasks that require a higher privelege level, then you would do so by requesting those priveleges as needed - such as instantiating a class with elevated permissions from a VFP COM server you've created. If you want more details on what all of the requirements are for UAC you can download a Word document provided by Microsoft.

OK, so does this mean that we can't elevate the execution level by changing the Application Manifest? No. You can do it, it is just that it isn't the way Microsoft wants you to do it and you won't have a prayer of getting one of those nice Vista Logos for your application if you care about that sort of thing. Well, not a prayer unless you are one of the lucky few that are provided with a special waiver by Microsoft because you've been able to argue your case for doing it well enough. I digress...

If you want to change the level then you have two other choices: highestAvailable and requireAdministrator. Now, the problem is that even if you know these there is currently no way in Visual FoxPro natively to change these. Let's look at a solution that was first explored by Calvin Hsia in one of his blog entries.

Changing the Application Manifest

If you read Calvin's blog you would have noted that among other things he is basically using the UpdateResource API call and doing some weird machinations with the end of the Visual FoxPro application. The UpdateResource API does exactly what its name implies. It updates a resource inside a PE file, and since the application manifest is a resource inside of a Visual FoxPro 9.0 application it can be used to update the application manifest. So what's with that stuff Calvin is doing where he goes to the end of the file and strips off a couple 14 byte structures? Well, near as Bo Durban and I can make out those two structures at the end of a Visual FoxPro PE file are used to point to a compiled VFP application and some typelib/registration information within the executable. You might have heard at one time or another that a VFP PE is different from a normal PE in that it is really just an executable that uses the VFP runtimes to run an internal FXP or APP. There seems to be some truth to that given Calvin's code. In any event, he strips out that VFP proprietary stuff to protect it, then when UpdateResource is done doing its thing he puts them back (though I believe he makes a tiny mistake and puts the stuff back in there backwards). Pretty slick huh?

So, by using the technique that Calvin outlined we can replace the Application Manifest inside a Visual FoxPro application and thus change the way our application behaves under UAC in Vista. This technique works for VFP9 SP1 as well as SP2, and for that matter it would work for previous versions of Visual FoxPro as well, but it's best if you just upgrade to VFP9 and come along with the rest of us for the ride.



Above: Screenshot of a Visual FoxPro Executable on Vista Ultimate that is marked with the default "asInvoker" execution level.



Above: Screenshot of a Visual FoxPro Executable on Vista Ultimate that is marked with the "requireAdministrator" execution level by modifying the application manifest that the exe contains. Note the "shield overlay" that Vista places on the exe's icon to denote that it requires admin priveleges.



Above: Automatic dialog that Vista Ultimate displays when the exe is marked with the "requireAdministrator" execution level. The user no longer needs to right-click on the exe in order to Run as Administrator. The elevated permissions are requested as soon as the exe is executed.


Reg-Free COM

In addition to changing the execution level of your application for Vista UAC, you can also use this technique to facilitate Reg-Free COM in Windows XP and Vista. Reg-Free COM is designed to alleviate DLL-Hell, specifically versioning and side-by-side execution. Also, if you are going to be using ClickOnce deployment in the future it helps since ClickOnce doesn't register COM components. Speaking of which, I'm giving a session on ClickOnce at Southwest Fox this year. I'd be more than happy to spend some time with anyone who attends going over this stuff in more detail (blog doesn't always do it justice), so if you're going to be free come October 18th, jump on over and register for the conference and I'll see you there. OK, as I was saying before I shamelessly plugged the super-awesome-not-to-be-missed Southwest Fox conference, we can actually use the application manifest to house the information in our Visual FoxPro applications that would usually need to be written into the registry by registering our COM components with regsvr32. What does this look like? Well, just like the security stuff was facilitated by adding a "trustInfo" node, the Reg-Free COM stuff is made possible by adding a "file" node. Let's see what it looks like...

<file name="regfree.dll">
        <comClass description="regfree.adder"
clsid="{99A4ADD8-5A6A-40C1-89FF-50B5E8267B69}"
progid="regfree.adder"
threadingModel="Both"/>
</file>

As you can see from the XML above there is a name attribute of the file node, in this case its value is "regfree.dll", then there is a nested (child) node called comClass where the description, clsid, progid, and threadingModel information for the OLEPublic class is contained. That's all there is to it. So, when this is added with the previous security modifications you should get an application manifest that looks somewhat like the following...

<?xml version="1.0" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity version="1.0.0.0" type="win32" name="Microsoft.VisualFoxPro" processorArchitecture="x86"/>
    <description>Visual FoxPro</description>
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" language="*" processorArchitecture="x86" publicKeyToken="6595b64144ccf1df"/>
        </dependentAssembly>
    </dependency>
    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
        <security>
            <requestedPrivileges>
                <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
            </requestedPrivileges>
        </security>
    </trustInfo><file name="regfreedll.dll">
        <comClass description="regfreedll.adder" clsid="{99A4ADD8-5A6A-40C1-89FF-50B5E8267B69}" progid="regfreedll.adder" threadingModel="Both"/></file></assembly>

A little on the messy side, but you get the idea and I save a little time not having to format that XML that I pulled from one of my VFP Exes. Now, with that application manifest in place, I wouldn't have to register the regfree.dll on any XP or Vista systems. Only if, god forbid, the user was running Windows 2000 or Windows 98 would my dll need to be registered. Regfree.dll only contains one class, the Adder class, but if it contained more then I would put in additional comClass nodes for each of the classes that I was using in my Exe, and if I have more than one DLL or OCX then I would add more file nodes. So there you have Reg-Free COM.

An Application Manifest Modifying Class

Since we don't have a way to do all of this natively in Visual FoxPro, I thought I would create a class to handle this for us. Presumably the class could be expanded on and could be used in conjunction with project hooks and a slick interface to really give us a professional way of handling this within the Visual FoxPro IDE. Also, this class will work on other PE Files, it has some VFP specific code in it, but that will only execute if the PE file you are modifying was compiled with VFP. For instance, you could run it against the vfp9.exe to make it always run in administrator mode while developing on Vista... that was just a for instance, it is completely against the EULA to do something like that, so please don't do that. I'm just saying, if it wasn't against the EULA, you could do something like that. :)

Next Up

In my next blog entry I'll take you through how to manipulate the String Table resource inside a PE File using Visual FoxPro and also show you how you can use the new COM Elevation Moniker and CoGetObject to facilitate instancing classes that require elevated permissions from a Visual FoxPro application running asInvoker. This will allow Visual FoxPro developers to run applications as they should on Windows Vista and only elevate the permissions as needed. More Vista compatibility and power to the developer.

Heads Up

Soon I'm going to debut a mini-filter I wrote in C and a user mode DLL that I wrote in C++ that will allow Visual FoxPro developers to fully encrypt and decrypt Visual FoxPro data on the fly. This will be somewhat like Cryptor does it, however I believe it to be more secure and, of course, it will be free. After that we'll take a look at rendering ActiveX controls, like MSChart directly to a report using an FLL I wrote with Bo Durban that one of my clients is being nice enough to let me share, and then we'll do some other crazy stuff. But for right now I am going to go nuts for awhile on Vista stuff. We've got a lot of other stuff to cover in this UAC stuff before I've shown you all the pieces, how they connect, and the code we need to use them. OK, I'll shut-up now and give you the code to the Application Manifest modifier class (just cut-n-paste the code into a prg, run it, and go select a VFP application you want to modify the manifest on - if you are going to be adding a COM server then be sure to change the path and name in the call to AddCOMModule method to your actual file).

Application Manifest Modification Class And Sample Code

*!* Sample Usage
LOCAL loModuleResourceEditor
m.loModuleResourceEditor = CREATEOBJECT("ModuleResourceEditor")
m.loModuleResourceEditor.Module = GETFILE("EXE|DLL|OCX|CPL")
m.loModuleResourceEditor.ExecutionLevel = 3 && Require Administrator
?m.loModuleResourceEditor.AddCOMModule("D:\Documents and Settings\Craig Boyd\Desktop\RegFreeComTest\regfreedll.dll")
?m.loModuleResourceEditor.CreateNewModuleManifest()
?m.loModuleResourceEditor.ApplyNewManifest()
?m.loModuleResourceEditor.AddStringToStringTable("RegFreeDll Adder",101)

DEFINE CLASS ModuleResourceEditor AS CUSTOM
    DIMENSION aCOMFiles(1) && array holding the COM modules to be added to manifest for REG-Free COM
    COMFileCount = 0 && Used internally to dimension aCOMFiles
    ExecutionLevel = 1 && asInvoker default
    Module = "" && EXE/OCX/DLL/etc. that will have its manifest modified
    Manifest = "" && New manifest
    DefaultManifest = "" && Manifest to be used if a manifest is not found in module
    LastError = 0 && Error number returned from GetLastError()
    
*******************************
    FUNCTION Init()
*******************************
    this.aCOMFiles(1) = .F.
ENDFUNC

*******************************
    FUNCTION AddCOMModule(tcCOMModuleName)
*******************************
        IF FILE(m.tcCOMModuleName)
            this.COMFileCount = this.COMFileCount + 1
            DIMENSION this.aCOMFiles(this.COMFileCount)
            this.aCOMFiles(this.COMFileCount) = m.tcCOMModuleName
            =ASORT(This.aCOMFiles,1,-1,0,1)
            RETURN .T.
        ELSE
            RETURN .F.
        ENDIF
    ENDFUNC

*******************************
    FUNCTION RemoveCOMModule(tcCOMModuleName)
*******************************
        LOCAL lnFoundAt
        m.lnFoundAt = ASCAN(this.aCOMFiles, m.tcCOMModuleName)
        IF m.lnFoundAt > 0
            this.aCOMFiles(m.lnFoundAt) = .F.
        ENDIF
    ENDFUNC

*******************************
    FUNCTION ApplyNewManifest()
*******************************
        #DEFINE RT_MANIFEST 24
        LOCAL hFile, lnPosition, lnSize, hModule, lcStruct, lnCounter, ;
        llVFPModule, lnResult, llReturn, lcManifestXML
        LOCAL ARRAY laVFPSections[2]
        m.llReturn = .F.
        IF !FILE(This.Module)
            *!* The Module property does not appear to be a file or has not been set.
            RETURN m.llReturn
        endif
        IF Empty(This.Manifest)
            *!* The Manifest property does not appear to have been set.
            RETURN m.llReturn
        endif
SET STEP ON
        DECLARE INTEGER BeginUpdateResource IN WIN32API STRING, INTEGER
        DECLARE INTEGER EndUpdateResource IN WIN32API INTEGER, INTEGER
        DECLARE INTEGER UpdateResource IN WIN32API INTEGER, INTEGER, INTEGER, INTEGER, STRING @, INTEGER
        DECLARE INTEGER GetLastError IN WIN32API

        m.llVFPModule = .T. && Assume that the module is a VFPModule
*!* Preserve APP stub and Typelib/Registration info
        m.hFile = FOPEN(This.Module)
        m.lnPosition = FSEEK(m.hFile,0,2) && Go EOF and Get Size
        FOR m.lnCounter = 1 TO 2
            =FSEEK(m.hFile, m.lnPosition - 14, 0)
            m.lcStruct = FREAD(m.hFile, 14)
            m.lnSize = CTOBIN(SUBSTR(m.lcStruct, 11, 4), "4sr") && last 4 bytes holds the size
            IF m.lnSize > m.lnPosition OR m.lnSize < 0 OR (m.lnCounter = 1 AND m.lnSize = 0)
                m.llVFPModule = .F.
                EXIT
            ENDIF
            IF m.lnSize != 0
                =FSEEK(m.hFile, m.lnPosition - m.lnSize, 0)
                m.laVFPSections[m.lnCounter] = FREAD(m.hFile, m.lnSize)
                IF m.lnCounter = 1 AND LEFT(m.laVFPSections[1],3) != 0hFEF2FF
                    m.llVFPModule = .F.
                    EXIT
                ENDIF
            ELSE
                m.laVFPSections[m.lnCounter] = m.lcStruct
            ENDIF
            m.lnPosition = m.lnPosition - m.lnSize
        ENDFOR
        =FCLOSE(m.hFile)

*!* Update Manifest in module
        m.hModule = BeginUpdateResource(This.Module, 0)
        m.lcManifestXML = This.Manifest
        m.lnResult = UpdateResource(m.hModule, RT_MANIFEST, 1, 1033, @m.lcManifestXML, LEN(m.lcManifestXML))
        IF EndUpdateResource(m.hModule, 0) = 0
            this.LastError = TRANSFORM(GetLastError())
        ELSE
            m.llReturn = .T.
        ENDIF

        IF m.llVFPModule
*!*     Restore APP stub and the Typelib
            m.hFile = FOPEN(This.Module, 2)
            m.lnPosition = FSEEK(m.hFile, 0, 2)
            FOR m.lnCounter = 2 TO 1 STEP -1
                =FWRITE(m.hFile, m.laVFPSections[m.lnCounter])
            ENDFOR
            =FCLOSE(m.hFile)
        ENDIF
        RETURN m.llReturn
    ENDFUNC

***********************
    FUNCTION AddStringToStringTable(tcString, tnStringIndex) && pass -1 for tnStringIndex to add to end of string table
***********************
        #DEFINE RT_STRING 6
        LOCAL hFile, lnPosition, lnSize, hModule, lcStruct, ;
        lnCounter, llVFPModule, lnResult, lnReturn, lcBaseStringTable, lcNewStringTable
        LOCAL ARRAY laVFPSections[2]
        
        m.lnReturn = -1
        
        IF !FILE(This.Module)
            *!* The Module property does not appear to be a file or has not been set.
            RETURN m.lnReturn
        endif
        IF Empty(m.tcString)
            *!* The parameter was not set.
            RETURN m.lnReturn
        endif

        DECLARE INTEGER BeginUpdateResource IN WIN32API STRING, INTEGER
        DECLARE INTEGER EndUpdateResource IN WIN32API INTEGER, INTEGER
        DECLARE INTEGER UpdateResource IN WIN32API INTEGER, INTEGER, INTEGER, INTEGER, STRING @, INTEGER
        DECLARE INTEGER GetLastError IN WIN32API

        m.llVFPModule = .T. && Assume that the module is a VFPModule
*!* Preserve APP stub and the Typelib
        m.hFile = FOPEN(This.Module)
        m.lnPosition = FSEEK(m.hFile,0,2) && Go EOF and Get Size
        FOR m.lnCounter = 1 TO 2
            =FSEEK(m.hFile, m.lnPosition - 14, 0)
            m.lcStruct = FREAD(m.hFile, 14)
            m.lnSize = CTOBIN(SUBSTR(m.lcStruct, 11, 4), "4sr") && last 4 bytes holds the size
            IF m.lnSize > m.lnPosition OR m.lnSize < 0 OR (m.lnCounter = 1 AND m.lnSize = 0)
                m.llVFPModule = .F.
                EXIT
            ENDIF
            IF m.lnSize != 0
                =FSEEK(m.hFile, m.lnPosition - m.lnSize, 0)
                m.laVFPSections[m.lnCounter] = FREAD(m.hFile, m.lnSize)
                IF m.lnCounter = 1 AND LEFT(m.laVFPSections[1],3) != 0hFEF2FF
                    m.llVFPModule = .F.
                    EXIT
                ENDIF
            ELSE
                m.laVFPSections[m.lnCounter] = m.lcStruct
            ENDIF
            m.lnPosition = m.lnPosition - m.lnSize
        ENDFOR
        =FCLOSE(m.hFile)
        SET STEP ON
        m.lcBaseStringTable = This.GetStringResource(1, RT_STRING)
        m.lnLastStringIndex = This.GetStringTableCount(m.lcBaseStringTable)
        DO case
        CASE m.tnStringIndex < 0
            m.tcString = This.GetFormattedStringForStringTable(m.tcString)
            m.lcNewStringTable = m.lcBaseStringTable + m.tcString
        CASE m.tnStringIndex > m.lnLastStringIndex
            m.tcString = This.GetFormattedStringForStringTable(m.tcString)
            m.lcNewStringTable = m.lcBaseStringTable + REPLICATE(0h0000, m.tnStringIndex - m.lnLastStringIndex) + m.tcString
        OTHERWISE
            m.lcNewStringTable = This.ReplaceStringTableString(m.lcBaseStringTable, m.tnStringIndex, m.tcString)
        ENDCASE
*!* Update String Table in module
        m.hModule = BeginUpdateResource(This.Module, 0)
        m.lnResult = UpdateResource(m.hModule, RT_STRING, 1, 1033, @m.lcNewStringTable, LEN(m.lcNewStringTable))
        IF EndUpdateResource(m.hModule, 0) = 0
            this.LastError = TRANSFORM(GetLastError())
        ELSE
            m.lnReturn = IIF(m.tnStringIndex < 0, m.lnLastStringIndex + 1, m.tnStringIndex)
        ENDIF

        IF m.llVFPModule
*!*     Restore APP stub and the Typelib
            m.hFile = FOPEN(This.Module, 2)
            m.lnPosition = FSEEK(m.hFile, 0, 2)
            FOR m.lnCounter = 2 TO 1 STEP -1
                =FWRITE(m.hFile, m.laVFPSections[m.lnCounter])
            ENDFOR
            =FCLOSE(m.hFile)
        ENDIF
        RETURN m.lnReturn
    ENDFUNC

***********************
    FUNCTION CreateNewModuleManifest()
***********************
        #DEFINE RT_MANIFEST 24
        LOCAL lcBaseManifest, lcLevelXML, lcModuleName, lnCounter, ;
            lcProgID, lcNameSpace, lcFileXML, lccomClassXML, lnClassCounter, ;
            loDOM AS MSXML2.DOMDocument, loParentNode AS MSXML2.IXMLDOMELEMENT, ;
            loNode AS MSXML2.IXMLDOMELEMENT, loNewLevelNode AS MSXML2.IXMLDOMELEMENT, ;
            loTempNode AS MSXML2.IXMLDOMELEMENT, loTypeLib AS TLI.TypeLibInfo
        IF !FILE(This.Module)
            *!* The Module property does not appear to be a file or has not been set.
            RETURN .F.
        ENDIF
        This.Manifest = ""
*!* Get Manifest from Module or Create one
        m.lcBaseManifest = This.GetStringResource(1, RT_MANIFEST)
        IF EMPTY(m.lcBaseManifest)
            IF EMPTY(this.DefaultManifest)
                m.lcBaseManifest = This.GetDefaultManifest()
            ELSE
                m.lcBaseManifest = this.DefaultManifest
            ENDIF
        ENDIF

        m.loDOM = CREATEOBJECT("MSXML2.DOMDocument.4.0")

        IF m.loDOM.LOADXML(m.lcBaseManifest)
            m.lcNameSpace = m.loDOM.firstChild.NEXTSIBLING.getAttribute("xmlns")
*!* Append or Replace Execution Level
            IF TYPE("This.ExecutionLevel") = "N"
                m.loNode = m.loDOM.selectSingleNode('//*[local-name()="trustInfo"]')
                IF !ISNULL(m.loNode)
                    m.loParentNode = m.loNode.parentNode
                    m.loParentNode.removeChild(m.loNode)
                ENDIF
                m.lcLevelXML = This.GetExecutionLevelXML()
                m.loNewLevelNode = This.GetNodeFromXML(m.lcLevelXML, m.lcNameSpace)
                IF !ISNULL(m.loNewLevelNode)
                    m.loDOM.selectSingleNode('//*[local-name()="assembly"]').appendChild(m.loNewLevelNode)
                ENDIF
            ENDIF

            m.loDOM.setProperty("SelectionNamespaces", "xmlns:pe='" + m.lcNameSpace + "'")
*!* Append or Replace Reg-Free COM
            IF TYPE("This.aCOMFiles",1) = "A"
                m.loTypeLib = CREATEOBJECT("TLI.TypeLibInfo")
                IF TYPE("m.loTypeLib") = "O"
                    FOR m.lnCounter = 1 TO ALEN(This.aCOMFiles,1)
                        IF TYPE("This.aCOMFiles(m.lnCounter)") = "C" AND FILE(This.aCOMFiles(m.lnCounter))
                            m.loTypeLib.ContainingFile = This.aCOMFiles(m.lnCounter)
                            FOR m.lnClassCounter = 1 TO m.loTypeLib.TypeInfoCount
                                m.loTypeInfo = m.loTypeLib.TypeInfos(m.lnClassCounter)
                                IF m.loTypeInfo.TypeKind = 5
                                    m.lcModuleName = JUSTFNAME(m.loTypeLib.ContainingFile)
                                    m.lcProgID = m.loTypeLib.NAME + "." + m.loTypeLib.TypeInfos(m.lnClassCounter).NAME
                                    m.loNode = m.loDOM.selectSingleNode('//*[local-name()="file"][translate(@name,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="' + LOWER(m.lcModuleName) + '"]')
                                    IF ISNULL(m.loNode)
                                        m.lcFileXML = This.GetCOMFileXML(m.lcModuleName)
                                        m.loNewLevelNode = This.GetNodeFromXML(m.lcFileXML, m.lcNameSpace)
                                        IF !ISNULL(m.loNewLevelNode)
                                            m.loNode = m.loDOM.selectSingleNode('//*[local-name()="assembly"]').appendChild(m.loNewLevelNode)
                                        ENDIF
                                    ELSE
                                        FOR EACH loTempNode IN m.loNode.childNodes
                                            IF m.loTempNode.nodeName = "comClass"
                                                IF LOWER(m.loTempNode.getAttribute("progid")) = LOWER(m.lcProgID)
                                                    m.loTempNode.parentNode.removeChild(m.loTempNode)
                                                    EXIT
                                                ENDIF
                                            ENDIF
                                        NEXT
                                    ENDIF
                                    m.lccomClassXML = This.GetcomClassXML(m.lcModuleName, m.lcProgID, m.loTypeInfo.GUID, m.loTypeInfo.HELPSTRING)
                                    m.loNewLevelNode = This.GetNodeFromXML(m.lccomClassXML, m.lcNameSpace)
                                    IF !ISNULL(m.loNewLevelNode)
                                        m.loNode.appendChild(m.loNewLevelNode)
                                    ENDIF
                                ENDIF
                            ENDFOR
                        ENDIF
                    ENDFOR
                ENDIF
            ENDIF
            This.Manifest = m.loDOM.XML
        ENDIF
        RETURN .T.
    ENDFUNC

***********************
    FUNCTION GetDefaultManifest()
***********************
        LOCAL lcReturn
        TEXT TO m.lcReturn textmerge noshow
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
    version="1.0.0.0"
    type="win32"
    name="<<tcModuleName>>"
    processorArchitecture="x86"/>
<description><<tcModuleName>></description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
language="*"
processorArchitecture="x86"
publicKeyToken="6595b64144ccf1df"/>
</dependentAssembly>
</dependency>
</assembly>
        ENDTEXT
        RETURN m.lcReturn
    ENDFUNC

***********************
    FUNCTION GetStringTableCount(tcStringTable)
***********************
LOCAL lnReturn, lnCounter, lnStringSize
m.lnReturn = 0
FOR m.lnCounter = 1 TO LEN(m.tcStringTable) STEP 2
    m.lnStringSize = CTOBIN(SUBSTR(m.tcStringTable,m.lnCounter,2),"2RS") * 2
    m.lnReturn = m.lnReturn + 1
    m.lnCounter = m.lnCounter + m.lnStringSize
ENDFOR
RETURN m.lnReturn
ENDFUNC

***********************
    FUNCTION GetStringTableString(tcStringTable, tnIndex)
***********************
LOCAL lnIndex, lnCounter, lnStringSize, lcReturn
m.lnIndex = 0
m.lcReturn = ""
FOR m.lnCounter = 1 TO LEN(tcStringTable) STEP 2
    m.lnStringSize = CTOBIN(SUBSTR(tcStringTable,m.lnCounter,2),"2RS") * 2
    m.lnIndex = m.lnIndex + 1
    IF m.lnIndex = m.tnIndex
        m.lcReturn = STRCONV(SUBSTR(m.tcStringTable, m.lnCounter + 2, m.lnStringSize),6)
        EXIT
    ENDIF
    m.lnCounter = m.lnCounter + m.lnStringSize
ENDFOR
RETURN m.lcReturn
ENDFUNC

***********************
FUNCTION GetFormattedStringForStringTable(tcString)
***********************
LOCAL lcReturn, lnLen
m.lnLen = LEN(m.tcString)
m.lcReturn = BINTOC(m.lnLen,"2RS") + STRCONV(m.tcString,5) && + IIF(MOD(m.lnLen,2) = 1,0h0000,"")
RETURN m.lcReturn
ENDFUNC

***********************
    FUNCTION ReplaceStringTableString(tcStringTable, tnIndex, tcReplacementString)
***********************
LOCAL lnIndex, lnCounter, lnStringSize, lcReturn
m.lnIndex = 0
m.lcReturn = ""
m.tnIndex = m.tnIndex + 1
FOR m.lnCounter = 1 TO LEN(tcStringTable) STEP 2
    m.lnStringSize = CTOBIN(SUBSTR(tcStringTable,m.lnCounter,2),"2RS") * 2
    m.lnIndex = m.lnIndex + 1
    IF m.lnIndex = m.tnIndex
        m.lcReturn = STUFF(m.tcStringTable, m.lnCounter, m.lnStringSize + 2, This.GetFormattedStringForStringTable(tcReplacementString))        
        EXIT
    ENDIF
    m.lnCounter = m.lnCounter + m.lnStringSize
ENDFOR
RETURN m.lcReturn
ENDFUNC

***********************
    FUNCTION GetStringResource(tnName, tnType)
***********************
        LOCAL lcModule, hModule, lcReturn, lnResource, lnSize, hGlobal
        DECLARE LONG LoadLibrary IN WIN32API STRING
        DECLARE LONG FindResource IN WIN32API LONG, LONG, LONG
        DECLARE LONG LoadResource IN WIN32API LONG, LONG
*!*            DECLARE LONG LockResource IN WIN32API LONG
        DECLARE LONG SizeofResource IN WIN32API LONG, LONG
        DECLARE LONG FreeLibrary IN WIN32API LONG
        DECLARE LONG FreeResource IN WIN32API LONG

        m.hModule = LoadLibrary(This.Module)
        m.lnResource = FindResource(m.hModule, tnName, tnType)
        m.hGlobal = LoadResource(m.hModule, m.lnResource)
        m.lnSize = SizeofResource(m.hModule, m.lnResource)
*!*    LockResource(m.lnMem)
        m.lcReturn = SYS(2600, m.hGlobal, m.lnSize)
        FreeResource(m.hGlobal)
        FreeLibrary(m.hModule)
        RETURN m.lcReturn
    ENDFUNC

***********************
    FUNCTION GetNodeFromXML(tcXML, tcNameSpace)
***********************
        LOCAL loDOMTemp AS MSXML2.DOMDocument, loNode AS MSXML2.IXMLDOMELEMENT
        m.loNode = NULL
        m.loDOMTemp = CREATEOBJECT("MSXML2.DOMDocument.4.0")
        IF m.loDOMTemp.LOADXML([<dummy xmlns="] + m.tcNameSpace + [">] + m.tcXML + [</dummy>])
            m.loNode = m.loDOMTemp.firstChild.firstChild
        ENDIF
        RETURN m.loNode
    ENDFUNC

*****************************
    FUNCTION GetExecutionLevelXML()
*****************************
*!*
*!* This.ExecutionLevel = 1 - asInvoker, 2 - highestAvailable, 3 - requireAdministrator
*!*
*!*    asInvoker — module executes with the same permissions as its parent process (formerly known as leastPrivilege)
*!* highestAvailable — module executes with the highest privileges the current user can obtain
*!* requireAdministrator — module executes only for administrators (includes shield overlay for module icon)
*!*
*****************************
        LOCAL lcLevel, lcReturn
        m.lcLevel = ICASE(This.ExecutionLevel = 2, "highestAvailable", This.ExecutionLevel = 3, "requireAdministrator", "asInvoker")
        TEXT TO m.lcReturn TEXTMERGE NOSHOW
    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
        <security>
            <requestedPrivileges>
                <requestedExecutionLevel level="<<m.lcLevel>>" uiAccess="false"/>
            </requestedPrivileges>
        </security>
    </trustInfo>
        ENDTEXT
        RETURN m.lcReturn
    ENDFUNC

*****************************
    FUNCTION GetCOMFileXML(tcModuleName)
*****************************
        LOCAL lcReturn
        TEXT TO m.lcReturn TEXTMERGE NOSHOW
<file name="<<LOWER(JUSTFNAME(tcModuleName))>>">
</file>
        ENDTEXT
        RETURN m.lcReturn
    ENDFUNC

*****************************
    FUNCTION GetcomClassXML(tcModuleName, tcProgID, tcCLSID, tcDescription)
*****************************
        LOCAL lcReturn && tcCLSID
*!*    m.lcCLSID = CLSIDFromProgIDEx(m.tcProgID)
        IF PCOUNT() < 4
            m.tcDescription = m.tcProgID
        ENDIF
        TEXT TO m.lcReturn TEXTMERGE NOSHOW
<comClass description="<<m.tcDescription>>"
clsid="<<m.tcCLSID>>"
progid="<<m.tcProgID>>"
threadingModel="Both"/>
        ENDTEXT
        RETURN m.lcReturn
    ENDFUNC

*****************************
FUNCTION GetLastErrorMessage(tnError)
*****************************
#DEFINE FORMAT_MESSAGE_FROM_SYSTEM 0x00001000
LOCAL lcBuffer
DECLARE INTEGER GetLastError IN win32api
DECLARE INTEGER FormatMessage IN kernel32.DLL ;
    INTEGER dwFlags, ;
    STRING @lpSource, ;
    INTEGER dwMessageId, ;
    INTEGER dwLanguageId, ;
    STRING @lpBuffer, ;
    INTEGER nSize, ;
    INTEGER Arguments
    m.lcBuffer = SPACE(128)
    =FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 'WINERROR.H', m.tnError, 0, @m.lcBuffer, 128 , 0)
    RETURN m.lcBuffer
ENDFUNC
ENDDEFINE


Tuesday, September 25, 2007 3:47:49 PM (GMT Daylight Time, UTC+01:00)  #    Comments [11]
Monday, October 29, 2007 3:03:52 AM (GMT Standard Time, UTC+00:00)
Craig,

I've been reading the fantastic multi-part write up you have shared here with great interest as I am working on UAC/Vista issues in some of my apps.

I tried the code provided on an VFP 9 SP2 compiled .EXE file and remarked the line that begins 'm.loModuleResourceEditor.AddCOMModule("D:\...' For some reason, the target .EXE gets reduced from 6.2mb to 32k; "5.0.9999.exe is not a Visual FoxPro .EXE file" after that.

PD: I wish I could have made it this year to SWFox!!!
Monday, October 29, 2007 6:20:07 AM (GMT Standard Time, UTC+00:00)
Yeah, I missed you at this year's SWFox. I have tested the code in this post on EXE files created with VFP 9.0 SP2 on Vista and it works as expected. So, can you send me a small repro via email? Thanks.

PS have you stepped through the code to see what is going on here?
Thursday, November 29, 2007 2:37:58 PM (GMT Standard Time, UTC+00:00)
Craig,

This is a very nice piece of work. Thank you. One small point. The manifest file has a resource id of 2 in a DLL, so the line of code:

m.lnRsrc = FindResource(m.hModule, 1, 24)

works just for executables.
Al Finkel
Friday, January 11, 2008 5:12:36 PM (GMT Standard Time, UTC+00:00)
Craig,

Do you now if one can call .Net created com components from foxpro using reg-free com? I just cant get it working.

Allard
Allard
Friday, January 11, 2008 8:18:41 PM (GMT Standard Time, UTC+00:00)
Hi Craig,

I tried your class with one of my VFP Executables. After that only a 24 KByte file remains of my originally 1100KByte EXE-File. What went wrong here?
Thanks.
Gerold
Saturday, January 12, 2008 12:06:13 AM (GMT Standard Time, UTC+00:00)
Al,

Thank you for the heads up. I'll be eventually creating an APP or EXE out of this code and will keep this in mind.

Allard,

I don't see any problems with this. Have you registered the .NET COM-visible assembly on your development machine? If not, try doing so, if you have registered it and it still doesn't work can you tell me more specifics about the problem(s) you are running into?

Gerold,

Is the VFP Exe that you are working with Encrypted? I didn't provide for this, if not can you step through the code and see where it is being truncated and provide more detailed information about the problem? If not, can you send me an executeable that will reproduce the problem at this end?
Monday, January 14, 2008 11:08:51 AM (GMT Standard Time, UTC+00:00)
Well I figured out that using .Net based com components with reg free com is a little bit different. I found this article ( http://msdn2.microsoft.com/en-us/library/eew13bza(VS.80).aspx ) about how to "Configure .NET-Based Components for Registration-Free Activation". I am trying to get it working at this moment. I will let it know if I succeed.

Allard
Allard
Tuesday, January 15, 2008 12:40:46 PM (GMT Standard Time, UTC+00:00)
Ok, I got it working. I made a little description on how to do it. I hope you don't mind me posting it here.

First export the manifest-file from vfp.exe. This should look like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
type="win32"
name="Microsoft.VisualFoxPro"
processorArchitecture="x86"
/>
<description>Visual FoxPro</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" />
</requestedPrivileges>
</security>
</trustInfo>

<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
language="*"
processorArchitecture="x86"
publicKeyToken="6595b64144ccf1df"
/>
</dependentAssembly>
</dependency>
</assembly>

Now add another <dependency> section to this file:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
type="win32"
name="Microsoft.VisualFoxPro"
processorArchitecture="x86"
/>
<description>Visual FoxPro</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" />
</requestedPrivileges>
</security>
</trustInfo>

<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
language="*"
processorArchitecture="x86"
publicKeyToken="6595b64144ccf1df"
/>
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity
name="ClassLibrary4"
version="1.0.0.0"
processorArchitecture="MSIL" />
</dependentAssembly>
</dependency>
</assembly>

save the file as vfp9.exe.manifest and make sure it is in the same directory as vfp9.exe

Note that when you try to start vfp9.exe it will give you a message that it is not configured correctly.

Now in vs2005 create a project named classlibrary4 and add a class to it named class1.vb. Put the following code in it:

<ComClass(ComClass1.ClassId, ComClass1.InterfaceId, ComClass1.EventsId)> _
Public Class ComClass1

#Region "COM GUIDs"
' These GUIDs provide the COM identity for this class
' and its COM interfaces. If you change them, existing
' clients will no longer be able to access the class.
Public Const ClassId As String = "00f5eab1-c160-4a07-8ac2-a0a000dae3a3"
Public Const InterfaceId As String = "17e92a69-2900-4cc3-a82c-eed517de3bb0"
Public Const EventsId As String = "d78b9869-2d39-44ee-9c11-7a0f27398fcf"
#End Region

' A creatable COM class must have a Public Sub New()
' with no parameters, otherwise, the class will not be
' registered in the COM registry and cannot be created
' via CreateObject.
Public Sub New()
MyBase.New()
End Sub

Public Function hello() As String
Return "hello world"
End Function

End Class

In the project properties choose “assembly information” and tick “make assembly com visible”


Compile the project.

Create a manifest file for this com component like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
name="ClassLibrary4"
version="1.0.0.0"
processorArchitecture="MSIL" />
<clrClass
clsid="{00F5EAB2-C160-4A07-8AC2-A0A000DAE3A3}"
progid="ClassLibrary4.ComClass1"
threadingModel="Both"
name="ClassLibrary4.ComClass1"
runtimeVersion="v2.0.50727">
</clrClass>
<file name="ClassLibrary4.dll">
</file>
</assembly>

Crucial here is that the <assemblyIdentity /> part is EXACTLY the same as in the vfp9.exe.manifest. If it isn’t, foxpro will not start (due to a configuration issue).

Import this manifest as a resource in classlibrary4.dll using mt.exe. The easiest is to add a post build event to vs2005 like this:

"C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin\mt.exe" -manifest "$(ProjectDir)$(TargetName).dll.manifest" -outputresource:"$(TargetDir)$(TargetName).dll;#1"

Copy the resulting file classlibrary4.dll to the directory with vfp9.exe and vfp9.exe.manifest in it.

Now start vfp9.exe and try to instantiate the com object like this:

loCom = Createobject('ClassLibrary4.ComClass1')

The object should be available, and you can call the “hello” method

? loCom.Hello()

On the foxpro screen there should be “hello world”

If you got it working it is easy to modify it to your needs. Instead of vfp9.exe.manifest you can put <myapp>.exe.manifest where <myapp> is the name of your compiled foxpro exe. Although not necessary on my machine you can include the manifest in the exe using craigs code. Including the manifest in the com server is necessary to get it working.
Allard
Saturday, January 19, 2008 4:43:51 PM (GMT Standard Time, UTC+00:00)
Hi Craig,

you were right, i use an encrypted exe file.
Good guess !

Will you add support for encrypted exe files?

Thanks for your help

Gerold
Gerold
Friday, April 11, 2008 6:51:56 PM (GMT Daylight Time, UTC+01:00)
Thanks Craig, very useful.

Allard: I've been trying to use Reg-free com with my vfp apps for a while now. I came across your post and Im trying to follow the steps you point out; I export the manifest-file from vfp.exe, add the dependency exactly as you put it and save the file in the same directory of vfp9.exe, but when I run vfp9.exe I DONT get a message saying that it is not configured correctly - am I missing something?

Thanks in advance
JD
JuanDiego
Tuesday, April 15, 2008 12:03:20 PM (GMT Daylight Time, UTC+01:00)
JD: maybe it is dependent on the OS you are using. On one vista machine we discovered that the <appname>.exe.manifest is ignored, and only the internal manifest is used. Maybe you should try to replace the internal manifest. There are some posts around telling how to do this with foxpro applications.

Are you sure that when you run vfp9.exe, the dependent component is NOT in the same directory? Only then you should get the message.
Allard
Comments are closed.

 

Archive

<July 2009>
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678