Silverlight SLlauncher.exe OOB Isolated Storage Quota bug workaround

The Problem

Silverlight 4 reserves 1MB for Isolated Storage when running in the browser, this is increased to 25MB when running Out-Of-Browser (OOB).

Enterprise deployment of OOB apps is done using SLlauncher.exe – straight from the Silverlight Enterprise Deployment Guide (.docx):

%ProgramFiles%Microsoft Silverlightsllauncher.exe /install:”xapFile” /origin:”xapURI”

HOWEVER… using this method to install an OOB application will NOT increase the quota for that application to 25MB! It stays at 1MB!

This is a really really frustrating and annoying bug in Silverlight 4 RTW (unsure of later versions).

The Workaround

The quotas for Silverlight Isolated Storage are actually stored as Hex values in a quota.dat file hidden deep in the USERPROFILE structure.

My workaround is to execute a console application immediately after installation to recurse through the folder structure until a recently created quota.dat file is found, and then to modify it writing HEX to the filestream… however another catch is that the application must have been run previously in order for the quota.dat to exist! So this is hacky to say the least… but it works!

Code as follows:

public static class FixQuota
    {

        public static void Run()
        {
            string strUserProfilePath = System.Environment.GetEnvironmentVariable("USERPROFILE");
            string strXpSlRoot = strUserProfilePath + @"Local SettingsApplication DataMicrosoftSilverlight";
            string strVistaSlRoot = strUserProfilePath + @"AppDataLocalLowMicrosoftSilverlight";
            string strSlRoot = string.Empty;

            if (Environment.OSVersion.Version.Major == 5)
            {
                if (Environment.OSVersion.Version.Minor == 1)
                {
                    // XP
                    strSlRoot = strXpSlRoot;
                }
                else if (Environment.OSVersion.Version.Minor == 2)
                {
                    // Server 2003 & XP 64-bit
                    strSlRoot = strXpSlRoot;
                }
            }
            else if (Environment.OSVersion.Version.Major >= 6)
            {
                // Vista+
                strSlRoot = strVistaSlRoot;
            }

            Log("Silverlight Root: " + strSlRoot);

            // Start the application; latest file in SL Root + OutOfBrowserindex is what we want:
            DirectoryInfo di = new DirectoryInfo(strSlRoot + @"OutOfBrowserindex");
            FileSystemInfo[] files = di.GetFileSystemInfos();

            // Sort files
            var sortedFiles = files.OrderByDescending(f => f.LastWriteTime);

            // Newest file
            var file = sortedFiles.ElementAt(0);
            Log("Found most recently modified file " + file.FullName);

            string strContents = string.Empty;
            // File structure is:
            // 123456789 http://domain.com/silverlight.xap
            // 789468796 http://domain.com/silverlight2.xap
            strContents = File.ReadAllText(file.FullName, Encoding.Unicode);

            // Grab the AppId
            string strAppId = Regex.Match(strContents, "([0-9]+)").Value;
            Log("strAppId: " + strAppId);

            // Grab the Domain e.g. domain.com
            string strDomain = Regex.Match(strContents, "http://([^/]*)/").Groups[1].Value;

            Log("Domain: " + strDomain);

            // Get program files path
            string strProgFilesPath = string.Empty;
            if( 8 == IntPtr.Size
                || (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432"))))
                strProgFilesPath = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
            else
                strProgFilesPath = Environment.GetEnvironmentVariable("ProgramFiles");

            var slLauncherPath = strProgFilesPath + @"Microsoft Silverlightsllauncher.exe";
            var appId = strAppId + "." + strDomain;

            Log("Launching " + slLauncherPath + " " + appId);

            // Launch the app
            var app = System.Diagnostics.Process.Start(slLauncherPath, appId);

            // Give Silverlight time to create Isolated Storage
            Thread.Sleep(4000);

            // Kill recently started app
            foreach (var program in System.Diagnostics.Process.GetProcessesByName("sllauncher"))
            {
                program.Kill();
            }

            // Give Silverlight time to recover
            Thread.Sleep(2000);

            // Isolated Storage quota file should be created now
            string strQuotaFile = FindQuotaFile(strSlRoot + @"is");

            if(strQuotaFile != string.Empty)
            {
                using (var stream = new FileStream(strQuotaFile, FileMode.Open, FileAccess.ReadWrite))
                {
                    stream.Position = 2;
                    stream.WriteByte(0xa0);
                    stream.Position = 3;
                    stream.WriteByte(0x01);
                }

                Log("Fixed file " + strQuotaFile + " - increased to 26MB");
            }

        }

        /// <summary>
        /// Recursive function to find most recently created quota.dat
        /// </summary>
        /// <param name="strRoot"></param>
        /// <returns></returns>
        private static string FindQuotaFile(string strRoot)
        {
            if (Directory.Exists(strRoot))
            {
                Log("Searching " + strRoot);
                string[] files = Directory.GetFiles(strRoot);
                foreach (string strFilename in files)
                {
                    if (strFilename.EndsWith("quota.dat"))
                    {
                        DateTime dtCreated = File.GetCreationTime(strFilename);
                        Log("Found quota.dat created " + dtCreated.ToString("s"));
                        if (dtCreated > DateTime.Now.AddMinutes(-10))
                        {
                            Log("Using " + strFilename);
                            return strFilename;
                        }
                    }
                }

                string[] dirs = Directory.GetDirectories(strRoot);
                foreach (string strDir in dirs)
                {
                    string strResult = FindQuotaFile(strDir);
                    if(strResult != string.Empty)
                    {
                        return strResult;
                    }
                }

            }

            return string.Empty;
        }

        private static void Log(string strMessage)
        {
            Console.WriteLine(strMessage);
        }
    }

I hope that helps somebody – enough people complaining about it on the Silverlight forums so thought I should post it up.

Comments welcome.

Leave a Comment Yourself

Your email address will not be published. Required fields are marked *