IOS – Enterprise App Installation on iOS that is too old

applicationsenterpriseinstallios

We have an enterprise app for which we are ending support of iOS6. During a recent test, we created a build of the app, and set the following values in Info.plist

<key>MinimumOSVersion</key>
<string>7.0</string>

The app was built with the iOS 7.1 SDK. If it matters (and I'm not certain that it does), it is a Xamarin.iOS project.

As we were testing, we expected that when we attempted to install on iOS6 that the installation would be rejected early in the process, since we were installing on an unsupported version of iOS. We anticipated that the previously installed version of the app would continue to function as it had before, without all of the new features.

Instead what happened was that the installation failure happened later in the installation than expected, and the old version of the app was unusable after the upgrade failed.

To double check a couple of things, I downloaded the IPA file, and unzipped it. Inside the bundled Info.plist, I see this:

MinimumOSVersion = "7.0";

which is a good sign. However I think that it also explains why the attempted installation broke the previously installed version.

For regular apps, i.e. apps from the iOS App Store, you aren't given the option of installing applications for which your installed iOS is too old, and the installed version of the app remains unchanged.

Is there a way to do such a thing for Enterprise Apps without having to build everything out? I'm thinking that maybe there is a setting in the manifest.plist that is used for OTA deployment.

For the sake of thoroughness, here is my Info.plist prior to build:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDisplayName</key>
    <string>OurApp Stage</string>
    <key>CFBundleIconFiles</key>
    <array>
        <string>Images/OurApp_72.png</string>
        <string>Images/OurApp_50.png</string>
        <string>Images/OurApp_29.png</string>
        <string>Images/OurApp_512.png</string>
    </array>
    <key>CFBundleIdentifier</key>
    <string>com.ourappproject.staging.mycompany</string>
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLName</key>
            <string>com.ourappproject.staging-handler</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>ourapp-staging</string>
            </array>
        </dict>
    </array>
    <key>CFBundleVersion</key>
    <string>2014.2.04.05</string>
    <key>MinimumOSVersion</key>
    <string>7.0</string>
    <key>UIBackgroundModes</key>
    <array>
        <string>audio</string>
    </array>
    <key>UIDeviceFamily</key>
    <array>
        <integer>2</integer>
    </array>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
</dict>
</plist> 

Info.plist from within the IPA:

// !!! BINARY PROPERTY LIST WARNING !!!
//
// The pretty-printed property list below has been created
// from a binary version on disk and should not be saved as
// the ASCII format is a subset of the binary representation!
//
{   BuildMachineOSBuild = "13C1021";
    CFBundleDevelopmentRegion = "en";
    CFBundleDisplayName = "OurApp Stage";
    CFBundleExecutable = "OurAppMobileApp";
    CFBundleIconFiles = ( "OurApp_72.png", "OurApp_50.png", "OurApp_29.png" );
    CFBundleIdentifier = "com.ourappproject.staging.mycompany";
    CFBundleInfoDictionaryVersion = "6.0";
    CFBundleName = "OurAppMobileApp";
    CFBundlePackageType = "APPL";
    CFBundleResourceSpecification = "ResourceRules.plist";
    CFBundleSignature = "????";
    CFBundleSupportedPlatforms = ( "iPhoneOS" );
    CFBundleURLTypes = (
        {   CFBundleURLName = "com.ourappproject.staging-handler";
            CFBundleURLSchemes = ( "ourapp-staging" );
        },
    );
    CFBundleVersion = "2014.2.04.05";
    DTCompiler = "com.apple.compilers.llvm.clang.1_0";
    DTPlatformBuild = "11D167";
    DTPlatformName = "iphoneos";
    DTPlatformVersion = "7.1";
    DTSDKBuild = "11D167";
    DTSDKName = "iphoneos7.1";
    DTXcode = "0510";
    DTXcodeBuild = "5B1008";
    LSRequiresIPhoneOS = :true;
    MinimumOSVersion = "7.0";
    UIBackgroundModes = ( "audio" );
    UIDeviceFamily = ( 2 );
    UIRequiredDeviceCapabilities = ( "armv7" );
    UISupportedInterfaceOrientations = ( "UIInterfaceOrientationLandscapeLeft", "UIInterfaceOrientationLandscapeRight" );
    "UISupportedInterfaceOrientations~ipad" = ( "UIInterfaceOrientationLandscapeLeft", "UIInterfaceOrientationLandscapeRight" );
}

The manifest.plist:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <!-- array of downloads. -->
        <key>items</key>
        <array>
            <dict>
                <!-- an array of assets to download -->
                <key>assets</key>
                <array>
                    <!-- software-package: the ipa to install. -->
                    <dict>
                        <!-- required.  the asset kind. -->
                        <key>kind</key>
                        <string>software-package</string>
                        <!-- required.  the URL of the file to download. -->
                        <key>url</key>
                        <string>https://arm.staging.ourappproject.com:443/Store/App/9/17/OurAppMobileApp.ipa</string>
                    </dict>
                    <!-- display-image: the icon to display during download .-->
                    <dict>
                        <key>kind</key>
                        <string>display-image</string>
                        <!-- optional.  indicates if icon needs shine effect applied. -->
                        <key>needs-shine</key>
                        <true/>
                        <key>url</key>
                        <string>https://arm.staging.ourappproject.com:443/Assets/OurAppMobileApp.png</string>
                    </dict>
                    <!-- full-size-image: the large 512x512 icon used by iTunes. -->
                    <dict>
                        <key>kind</key>
                        <string>full-size-image</string>
                        <key>needs-shine</key>
                        <true/>
                        <key>url</key>
                        <string>https://arm.staging.ourappproject.com:443/Assets/OurApp_512.png</string>
                    </dict>
                </array>
                <key>metadata</key>
                <dict>
                    <!-- required -->
                    <key>bundle-identifier</key>
                    <string>com.ourappproject.staging.mycompany</string>
                    <!-- optional (software only) -->
                    <key>bundle-version</key>
                    <string>2014.2.04.05</string>
                    <!-- required.  the download kind. -->
                    <key>kind</key>
                    <string>software</string>
                    <!-- optional. displayed during download; typically company name -->
                    <key>subtitle</key>
                    <string>mycompany</string>
                    <!-- required.  the title to display during the download. -->
                    <key>title</key>
                    <string>OurApp Mobile</string>
                </dict>
            </dict>
        </array>
    </dict>
</plist>

Any and all suggestions are appreciated.

Best Answer

I don't think it is possible to do.

If you are using a web based deployment you can always check for an OS version from the browser and force it that way.

You can use Request.UserAgent to get the OS version and then JQuery to disable the button and throw an alert explaining to the user why they can't download.

        string agent = Request.UserAgent;


        if (agent.Contains("iPad; CPU OS 6_"))
        {
            ClientScript.RegisterStartupScript(this.GetType(), "iOS",
                                               " $('#appInstall a').click(function(e) {e.preventDefault();});",
                                               true);
            ClientScript.RegisterStartupScript(this.GetType(), "iOSAlert",
                                               " $('#appInstall a').click(function() {alert('The application is now only available on iPads running iOS 7 or higher. Please upgrade your iPad to get the latest version.');});",
                                               true);
        }