Tuesday, May 31, 2011

Setting SSL port bindings in IIS7 with Installshield 2011

Improved code formatting at my new blog: http://growlycode.wordpress.com/2012/03/22/setting-ssl-port-bindings-in-iis7-with-installshield-2011/

Ok, so it's another non-Flex-related post, but I've been doing a lot of .NET work lately, so I'm going to sneak a bit of it in here.

I finally solved the issue I was having setting HTTPS port bindings in InstallShield 2011.

First off, you can't do this via the InstallShield UI for IIS 7. There's an option "SecureBindings" under "Other IIS Properties" in the InstallShield website UI, but this only works for IIS 6. If you want pure, unadulterated IIS 7 support, you need a custom action (CA) that manually sets the port using InstallScript or VBScript.

Caveat emptor!! I am not a windows installer developer. It took me a lot of trial and error to get this going.
If you have a suggestion of a more correct or nicer way of doing things, please leave me a comment.

In the meantime, in the hope that it will save someone else from having to do the same thing, here is what I eventually came up with:

Create an InstallScript CA, called SetSecurePortBindings
This CA will need to be "Deferred" in order to run after the IIS installation is done.
In deferred mode, you won't have access to any non-public installer properties, so if you're setting your ports from properties (e.g. [MYSITE_HTTPS_PORT]) that you set from a config or user dialog input, you'll need to make sure that you store any props you need in your deferred CA in the CustomActionData property for that CA.
I scheduled the custom action After InstallServices but any time after IIS install should be fine. My CA calls the SetSecurePortBindings function.




SetSecurePortBindings then makes a call to ensure that the current IIS version is not IIS 6 (we only support IIS6 and 7). If it is, the function will return, as the values will be set via the SecureBindings entry. If not, the function proceeds to read the site names and port numbers from the CustomDataAction. As I have a number of sites to handle, I have passed a delimited list of properties and am using the string tokeniser function to parse them out into a list.  Then, for each SiteName;PortBinding pair, I call the SetSecurePortBinding(hMSI, siteName, httpsPort) function. 


export prototype SetSecurePortBindings(HWND);
prototype SetSecurePortBinding(HWND, STRING, STRING);
prototype int IISRTGetIISVersion();

/**
 * Sets secure http port bindings using AppCmd.exe. Required for IIS7.
 * This script should be called from a Deferred Custom Action (After InstallServices in my case) and therefore
 * will need to extract any installer properties from the CustomActionData property.
 * The CustomActionData property is populated via a property-set CA (After InstallInitialize),
 * which sets the values of the properties that need to be passed along into a property by the
 * same name as the utilizing CA.
 */
function SetSecurePortBindings(hMSI)
    STRING customActionData, name, httpsPort;
    LIST listProps;
    NUMBER nvSize, nResult, nTokenCount, nCount;
    int nIISVersion;
begin
    SprintfMsiLog("SetSecurePortBindings: %s", "Entered SetSecurePortBindings");   
    if MAINTENANCE != TRUE
    then
        nvSize = 256;
       
        SprintfMsiLog("SetSecurePortBindings: %s", "Non-maintenance operation, proceeding with CA");   

        // If IIS, return, this will be handled by the SecureBindings IIS6 Metabase compatibility
        // module
        nIISVersion = IISRTGetIISVersion() ;
        SprintfMsiLog("SetSecurePortBindings: Detected IIS version '%i'", nIISVersion);   

        if (nIISVersion == 6) then
            SprintfMsiLog("SetSecurePortBindings: IIS6 detected. Returning, as, this will be handled by the SecureBindings IIS6 metabase entry", "");   
            return ERROR_SUCCESS;
        endif;

        // CustomActionData stores the sites and https ports to be configured, in a ; delimited list
        // e.g. Sitename1;*:443:;Sitename2;*:444:
        MsiGetProperty (hMSI, "CustomActionData", customActionData, nvSize);
       
        // Split the properties on the semi-colon delimiter
        listProps = ListCreate (STRINGLIST);
        if (StrGetTokens (listProps, customActionData, ";") > 0) then
            SprintfMsiLog("SetSecurePortBindings: Failed to get tokens from CustomActionData content %s", customActionData);   
        else
       
            nTokenCount = ListCount(listProps);
            SprintfMsiLog("SetSecurePortBindings: Found %i tokens in CustomActionData content %s", nTokenCount, customActionData);   
           
            // Iterate through each block of two (name;port) and call the SetSecurePortBinding method
            nCount = 0;
            while (nCount < nTokenCount)

                if (nCount == 0) then
                    ListGetFirstString(listProps,name);
                else
                    ListGetNextString(listProps,name);
                endif;
                ListGetNextString(listProps,httpsPort);
               
                SetSecurePortBinding(hMSI, name, httpsPort);   
                nCount = nCount + 2;
               
            endwhile;
        endif;
    else
        SprintfMsiLog("SetSecurePortBindings: %s", "Maintenance operation. Skipping...");   
    endif;
end;


/**
 * Returns IIS version detected via registry key
 */
function int IISRTGetIISVersion()
    NUMBER nResult, nVersion, nType, nSize;
    STRING szName, szValue;
begin

    SprintfMsiLog("SetSecurePortBindings: %s", "Attempting to find IIS major version");   

    RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE );

    szName = "MajorVersion";

    nSize = -1;
    nType = REGDB_NUMBER;
    nResult = RegDBGetKeyValueEx("SYSTEM\\CurrentControlSet\\Services\\W3SVC\\Parameters", szName, nType, szValue, nSize);
    if (ISERR_SUCCESS != nResult) then
        return nResult;
    endif;

    nResult = StrToNum(nVersion, szValue);
    if (ISERR_SUCCESS != nResult) then
        return nResult;
    endif;

    RegDBSetDefaultRoot(HKEY_CURRENT_USER );

    return nVersion;
end; 
The SetSecurePortBinding function calls the appcmd.exe util (the new MS-sanctioned way to interact with IIS7) to set the secure port for the named site.

/**
 * Calls AppCmd.exe to set https port bindings
 */
function SetSecurePortBinding(hMSI, svSiteName, svHttpsBinding)
    STRING strMsg, svCall, svCmd, svProg, svParms[500], svOut;
    NUMBER nResult, nLineNum, n;

begin

    SprintfMsiLog("SetSecurePortBindings: Attempting to set port bindings for site '%s' to %s", svSiteName, svHttpsBinding);   

    // IIS 7 setup
    // Check for the correct application pool first
    svCmd = "cmd";
    svProg = " /C " + WINDIR ^ "system32" ^ "inetsrv" ^ "appcmd.exe";
    svCall = " set site /site.name: "+svSiteName+" /+bindings.[protocol='https',bindingInformation='"+svHttpsBinding+"']";   
    svParms = svProg + svCall;
   
    SprintfMsiLog("SetSecurePortBindings: Executing 'cmd %s'", svParms);
    nResult = LaunchAppAndWait(svCmd, svParms, WAIT | LAAW_OPTION_HIDDEN);

    if ( nResult == -1 ) then
        SprintfMsiLog("SetSecurePortBindings: Failed to set set binding. Return value %i", nResult);
        return nResult;
    endif;
   
    SprintfMsiLog("SetSecurePortBindings: %s", "Binding successfully set");
   
    return ERROR_SUCCESS;
end;

Monday, May 30, 2011

How to fix "The specified service has been marked for deletion"

Well, chances are, you're trying to uninstall a windows service.
This error tends to happen when you don't stop the service before attempting to uninstall it or/and some associated process or handle hangs around, preventing the service from being uninstalled.

One common culprit is services.msc - it's particularly bad because if you've been a good little monkey and stopped the service before running your uninstaller, you probably still have the services.msc window open.

Before you go restarting the machine etc, MAKE SURE YOU CLOSE SERVICES.MSC, in your session and any other active rdp session.

If you close and reopen the console, you'll probably find that your service has now been successfully deleted.

Sunday, May 29, 2011

Creating the ASP.NET Session State DB

So I know where to look this up the next time I re-forget it, the following cmd creates a session state db using integrated security.


C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_regsql.exe -ssadd -sstype c -d ASPState -S (local) -E 

Where:

S - db host/instance
E - use integrated security



Ensure that the login is also granted sysadmin privileges

To add the appropriate permissions:


use ASPState
grant EXECUTE on GetHashCode to "DOMAIN\Username"
grant EXECUTE on GetMajorVersion to "DOMAIN\Username"
grant EXECUTE on TempGetAppID to "DOMAIN\Username"
grant EXECUTE on TempGetStateItem to "DOMAIN\Username"
grant EXECUTE on TempGetStateItem2 to "DOMAIN\Username"
grant EXECUTE on TempGetStateItem3 to "DOMAIN\Username"
grant EXECUTE on TempGetStateItemExclusive to "DOMAIN\Username"
grant EXECUTE on TempGetStateItemExclusive2 to "DOMAIN\Username"
grant EXECUTE on TempGetStateItemExclusive3 to "DOMAIN\Username"
grant EXECUTE on TempGetVersion to "DOMAIN\Username"
grant EXECUTE on TempInsertStateItemLong to "DOMAIN\Username"
grant EXECUTE on TempInsertStateItemShort to "DOMAIN\Username"
grant EXECUTE on TempInsertUninitializedItem to "DOMAIN\Username"
grant EXECUTE on TempReleaseStateItemExclusive to "DOMAIN\Username"
grant EXECUTE on TempRemoveStateItem to "DOMAIN\Username"
grant EXECUTE on TempResetTimeout to "DOMAIN\Username"
grant EXECUTE on TempUpdateStateItemLong to "DOMAIN\Username"
grant EXECUTE on TempUpdateStateItemLongNullShort to "DOMAIN\Username"
grant EXECUTE on TempUpdateStateItemShort to "DOMAIN\Username"
grant EXECUTE on TempUpdateStateItemShortNullLong to "DOMAIN\Username"
go

Friday, May 27, 2011

I'm ACE...

By "ACE", I mean that as of 8:45am this morning, I'm an Adobe Certified Expert in Flex 3 with AIR.

The exam was interesting.

To be honest, I think it'd be relatively easy to fail even for an experienced dev. A lot of the questions are basic, but some are tricky purely because they're so specific. I don't know about the rest of you, but I don't tend to keep screeds of syntax and namespaces in my head. I generally know vaguely what I  need, and what it's probably called (or should be called)... Intellisense gets the me rest of the way there. And if there was anything I was unsure of, a google would remind me quickly enough.

The ACE exam is not at all like that. You WILL get questions about class/event/method names, and it doesn't matter how easy it is to look it up in the real world. Moral of the story, kids - don't make the mistake of thinking that just because you're a good Flex developer and work with Flex 8 hours a day, you'll be sweet.

At least scan EVERYTHING in the livedocs or you're going to get thrown by obscure questions about the AIR update process or the correct name of the event dispatched in blah situation. Or something else you've never used before, won't ever use, and don't much care about.

You don't find out at the end which questions you got right/wrong, just your overall percentages. The questions I assume I got right, I knew without a shadow of a doubt. The ones I didn't know, I really didn't know, no idea whatsoever - any I got right out of the ones I "flag[ged] for review" were dumb luck.


Anyway, onwards and upwards!

Now I'm quite looking forward to my Flex 4 exam next month. These days I'm far more current with the Gumbo/Hero nitty-gritty than with Flex 3 and I've been itching for a chance to do a Flex 4 skinning deep-dive. Muwahaha. Should be great...

Monday, May 2, 2011

Getting your application version number in AIR 2.6

 The following code reads the application version from the appname-app.xml file in your project directory. Note that your app version is now stored in the "versionNumber" tag - a slight departure from the older AIR syntax where the version was stored in a "version" tag.

var descriptor:XML = NativeApplication.nativeApplication.applicationDescriptor;
var ns:Namespace = descriptor.namespaceDeclarations()[0];
var version:String= descriptor.ns::versionNumber;

Flex 4.5 - from excitement to disappointment

OK. Now I'm sad. Really sad.
I'd been reading up on Flex Hero/FB Burrito and the new Air 2.6 release, and tbh I thought it might be the answer to all my PhoneGap issues. A Flex rewrite! YEAH! I was stoked! I wanted to get started right away.

THEN I realized there's no access to native device info like the UDID or OS version. Damn. I could probably do without the OS version, but the lack of a UDID is a deal-breaker for us, as we use it for one-device-one-app checks.

I know the wider solution to this problem is for Adobe to allow native calls and callbacks, but really I'd settle for just the ability to get the UDID.

Sigh. Looks like we're keeping the PhoneGap. Sniff sniff.