4

I try to write an app, it can download an application from web server ( ipa file) and install it. Does anyone know how to install this ipa file ? Can i do it using OTA inside app or use command line to install it ?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Quang Huynh
  • 345
  • 1
  • 4
  • 13
  • 3
    You can do that on a jailbroken device. Is such a solution acceptable? –  Feb 22 '13 at 07:55
  • Yes, it's acceptable, how can i do it ? – Quang Huynh Feb 22 '13 at 08:13
  • @QuangHuynh Have you tried searching? – trojanfoe Feb 22 '13 at 08:26
  • Perhaps you can look for Testflight. They provide a Certificate for their own app which is able to install other IPA files onto the device OTA. – who9vy Feb 22 '13 at 08:33
  • @trojanfoe i've search a lot but only found OTA, or install ipa file with iFunbox, iTools ...or ipa installer console via terminal in jailbroken device. – Quang Huynh Feb 22 '13 at 08:53
  • What do you try to achieve? What is the purpose of doing that? Depending on your answer to this question, an enterprise account rather tahn a regular developer account may be the right thing for you. – Hermann Klecker Feb 22 '13 at 14:09
  • @who9vy I am sorry, but this is wrong. That profile has nothing to do with being able to install other IPA files. It allows their website to know the current devices UDID, so they can search in their database which apps it can install by searching that UDID in the provisioning profiles of all the apps they have. The profile does **NOT** allow you to install any IPA file! – Kerni Feb 22 '13 at 15:32
  • @QuangHuynh I've posted a solution for jailbroken devices that doesn't involve OTA and can be natively integrated to your app. –  Feb 22 '13 at 15:36

2 Answers2

6

So here's an instant solution for jailbroken devices, which makes it possible to directly install any .ipa file from within your application. The steps you have to take is:

I. Gain root access. You can achieve this by calling setuid(0); from your main() function. You'll need to set the sticky permission bit on your executable and use a startup script too.

II. Unzip the .ipa file. Yes, that's right - IPAs are just disguised ZIP files. You can use the opensource libzip library for this.

III. There'll be a directory called Payload inside. The actual app bundle (let's call it MyApp.app) will reside in that folder.

IV. Create a directory in the /var/mobile/Applications directory on the filesystem. This will be the container sandbox of the app to be installed. By convention, the name of this directory should be an UUID. For example, you can use the following code snippet for this:

CFUUIDRef uuidObj = CFUUIDCreate(NULL);
CFStringRef uuid = CFUUIDCreateString(NULL, uuidObj);
CFRelease(uuidObj);

NSString *appPath = [@"/var/mobile/Applications" stringByAppendingPathComponent:(id)uuid];
[fmgr createDirectoryAtPath:appPath withIntermediateDirectories:YES attributes:nil error:NULL];
CFRelease(uuid);

V. Find the app bundle by looping through the contents of the Payload directory (obtained in step II). Copy it over to the newly created sandbox (of which the name is the UUID string). Also copy the iTunesMetadata.plist and iTunesArtwork files in order iTunes to display a nice icon for the app and to notify you of updates. Fix the permissions of the executable of the application as well to make it executable:

NSString *execName = [appInfoPlist objectForKey:@"CFBundleExecutable"];
NSString *execPath = [bundle stringByAppendingPathComponent:execName];
chmod(execPath.UTF8String, 0755);

VI. You'll need to tell SpringBoard to locate your app and then to reload its installed app cache to make the icon of the freshly installed appear on the home screen. For this, you first update the list of the applications in the MobileInstallation property list file. Here the bundle variable refers to the filesystem location of the app bundle, something like /var/mobile/applications/LONG_UUID_STRING/MyApp.app.

#define kMobileInstallationPlistPath @"/var/mobile/Library/Caches/com.apple.mobile.installation.plist"
NSMutableDictionary *appInfoPlist = [NSMutableDictionary dictionaryWithContentsOfFile:[bundle stringByAppendingPathComponent:@"Info.plist"]];
[appInfoPlist setObject:@"User" forKey:@"ApplicationType"];
[appInfoPlist setObject:bundle forKey:@"Path"];
[appInfoPlist setObject:@{
    @"CFFIXED_USER_HOME" : appPath,
    @"HOME" : appPath,
    @"TMPDIR" : [appPath stringByAppendingPathComponent:@"tmp"]
} forKey:@"EnvironmentVariables"];
[appInfoPlist setObject:appPath forKey:@"Container"];

NSData *data = [NSData dataWithContentsOfFile:kMobileInstallationPlistPath];
NSMutableDictionary *mobileInstallation = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:NULL error:NULL];
NSString *bundleID = [appInfoPlist objectForKey:@"CFBundleIdentifier"];
[[mobileInstallation objectForKey:@"User"] setObject:appInfoPlist forKey:bundleID];
[mobileInstallation writeToFile:kMobileInstallationPlistPath atomically:NO];

Then remove the cached app information SpringBoard stores:

remove("/var/mobile/Library/Caches/com.apple.mobile.installation.plist");
remove("/var/mobile/Library/Caches/com.apple.springboard-imagecache-icons");
remove("/var/mobile/Library/Caches/com.apple.springboard-imagecache-icons.plist");
remove("/var/mobile/Library/Caches/com.apple.springboard-imagecache-smallicons");
remove("/var/mobile/Library/Caches/com.apple.springboard-imagecache-smallicons.plist");
remove("/var/mobile/Library/Caches/SpringBoardIconCache");
remove("/var/mobile/Library/Caches/SpringBoardIconCache-small");
remove("/var/mobile/Library/Caches/com.apple.IconsCache");

Then notify SpringBoard to reload the list of all applications:

Class __LSApplicationWorkspace = objc_getClass("LSApplicationWorkspace");
[(LSApplicationWorkspace *)[__LSApplicationWorkspace defaultWorkspace] invalidateIconCache:nil];
[(LSApplicationWorkspace *)[__LSApplicationWorkspace defaultWorkspace] registerApplication:[NSURL fileURLWithPath:bundle]];
notify_post("com.apple.mobile.application_installed");
  • i've tried to get root by setuid(0) but i can't create folder inside /var/mobile/applications, could you help me ? Thanks. – Quang Huynh Jun 05 '13 at 03:16
  • @QuangHuynh So did you set the sticky bit on your executable and used a startup shell script? It doesn't work without those. –  Jun 05 '13 at 07:40
  • @H2C03 : how can i do this ? i'm new to iOS, thank you so much. – Quang Huynh Jun 05 '13 at 11:52
  • @QuangHuynh This is not iOS-specific, you just need a normal shell script and `chmod 7755` on your executable. –  Jun 05 '13 at 11:52
  • @H2C03 could you give me a tutorial about this, im feel ambiguous about this, how can i find the path to my executable file,... ? – Quang Huynh Jun 05 '13 at 12:05
  • @QuangHuynh `/Applications/.app/YourAppName` –  Jun 05 '13 at 12:17
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/31269/discussion-between-quang-huynh-and-h2co3) – Quang Huynh Jun 05 '13 at 15:43
  • @QuangHuynh is there any success, I am also stuck in the same but hard luck. Can you please share some of the experience? – Deepika Lalra Jun 18 '13 at 08:35
  • @DeepikaLalra i havn't finished yet, i've found another way to solved my problem. – Quang Huynh Jun 19 '13 at 09:27
  • @QuangHuynh thanks for the reply can you please give me some idea how to do that? That will be great. – Deepika Lalra Jun 19 '13 at 10:26
  • @H2CO3 I follow your instructions above and got stuck at step VI where I cannot overwrite the installplist file, is there some more thing else I should perform to complete this? I appreciate your help. – Sovannarith Jun 20 '13 at 15:43
  • @QuangHuynh am so glade that you found another way to solve the problem. Am just like Deepika Lara, I wish you can share this with us.Thanks. – Sovannarith Jun 20 '13 at 15:45
  • @sovannarith Sure your process has root? –  Jun 20 '13 at 16:17
  • @sovannarith i've used a private framework MobileInstallation for ipa installation, try https://github.com/kryhear/IPAInstaller , but i still dont know how to make root process, if you know how, could you share with me ? – Quang Huynh Jun 21 '13 at 02:33
  • @QuangHuynh It give me the error (MobileInstallationInstall) pMobileInstallationInstall = any idea? – Deepika Lalra Jun 21 '13 at 06:24
  • @QuangHuynh I've got message asking to check if my device was jailbroken while I installed the app in the jailbroken device already. This is hard. – Sovannarith Jun 21 '13 at 07:28
  • @H2CO3 I did put setuid(0) in the main function and set the stick permission you told in step one by this link http://stackoverflow.com/questions/7841344/error-permission-denied-when-accessing-ios-filesystem-jailbreak/8796556#8796556 but still not work. Am I missing something? – Sovannarith Jun 21 '13 at 07:33
  • @sovannarith Got the startup shell script as well? –  Jun 21 '13 at 08:03
  • @H2CO3 I did create a copy of app shell script myApp and renamed it to myApp2, replace content of old script with something I mentioned in the link, and change permission and ownership before ssh into device. – Sovannarith Jun 21 '13 at 08:10
  • @sovannarith And the shell script calls `exec "/Applications/MyApp.app/RealExecutable;"`, and in the Info.plist, the `CFBundleExecutable` key is pointing to the shell script, right? –  Jun 21 '13 at 08:11
  • @H2CO3 There's something you need to know. If my process not gaining root privilege I won't be able to create the bundle for the app to install "/var/mobile/Applications/#####", isn't it? – Sovannarith Jun 21 '13 at 08:27
  • @sovannarith Well, that folder belongs to user "mobile", so it may not be the case, but how is that related? –  Jun 21 '13 at 08:28
  • @H2CO3 I thought if It's the case it mean that I already have root access, cos the bundle was created. – Sovannarith Jun 21 '13 at 08:36
  • @sovannarith What does `getuid()` return? `0` or `501`? –  Jun 21 '13 at 08:36
  • @H2CO3 Actually I don't know since it's not attache to Xcode. – Sovannarith Jun 21 '13 at 08:42
  • @QuangHuynh Can you please join this room http://chat.stackoverflow.com/rooms/32136/chat, as just want to discuss a few things related to this topic? – Deepika Lalra Jun 21 '13 at 08:55
  • @sovannarith I don't see your problem there. You don't need all of Xcode for this at all. I just asked you for the return value of a function. –  Jun 21 '13 at 09:25
  • @QuangHuynh I have used the same but its showing error "MobileInstallationInstall: failed with -1" so any idea for that?Secondly If the ipa is composed of that provisioning profile that does not contains UDID of the target device so will this code work? – Deepika Lalra Jun 24 '13 at 05:21
  • 1
    @DeepikaLalra you should try http://stackoverflow.com/questions/13817569/how-to-programatically-install-a-ipa-file-in-ios-6 for solution ? – Quang Huynh Jun 24 '13 at 09:00
  • @QuangHuynh thanks for the response and yes this is the exact error that I was facing. Also this is the case where we created the ipa by our own but what if client has a already existing store of IPA so I can't do modification to existing file now if I just have to download and install them how should I proceed ? – Deepika Lalra Jun 24 '13 at 09:29
  • @DeepikaLalra I don't have time to try it yet, if there's result I'll share. – Sovannarith Jun 24 '13 at 10:40
  • @QuangHuynh Regarding to gain root access for your app, please have a look at this answer http://stackoverflow.com/questions/7841344/error-permission-denied-when-accessing-ios-filesystem-jailbreak/8796556#8796556 I did get root access with this answer. – Sovannarith Jun 24 '13 at 10:42
  • @H2CO3 I did get the root privilege, the result of getuid() is 0. But when I continue to step VI, the file "/var/mobile/Library/Caches/com.apple.mobile.installation.plist" was not found. Any suggestion? – Sovannarith Jun 25 '13 at 03:31
  • @sovannarith Well, that's strange, I have it on all of my devices, and I've successfully tested this technique in a real app. Haven't you made a typo? If not: what if you try to create the file if nonexistent? –  Jun 25 '13 at 05:54
  • @H2CO3 I did found it last time in that path. Isn't it recreated every times after refresh the springboard? – Sovannarith Jun 25 '13 at 06:00
  • @sovannarith No, it isn't (AFAIK). –  Jun 25 '13 at 06:04
  • @H2CO3 Is this line of code, step VI, remove the file? remove("/var/mobile/Library/Caches/com.apple.mobile.installation.plist"); – Sovannarith Jun 25 '13 at 06:08
  • @sovannarith Yes, it does. It's the C standard library function for deleting a file. –  Jun 25 '13 at 06:09
  • @H2CO3 So the file was deleted at the end of installation, why it exists in your case? – Sovannarith Jun 25 '13 at 06:12
  • @sovannarith IDK, presumably SpringBoard recreated it. So have you tried commenting out that line and see if it works? –  Jun 25 '13 at 06:13
  • @H2CO3 I feel like something wrong in my code that springboard didn't recreate it, and now it was already deleted. What should I do? – Sovannarith Jun 25 '13 at 06:16
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/32310/discussion-between-sovannarith-and-h2co3) – Sovannarith Jun 25 '13 at 06:18
  • @H2CO3 Ok I'll do it. But can I ask you some more? – Sovannarith Jun 25 '13 at 06:23
  • @H2CO3 When you notify SpringBoard to reload the list of all applications at the last step, what else you did beside writing that bunch of codes? – Sovannarith Jun 25 '13 at 06:26
  • @sovannarith I waited until it completed. (Well **what** do you expect? Is there some big secret I would keep for myself and not post it in the answer? Or what?) –  Jun 25 '13 at 06:27
  • @H2CO3 I didn't mean like that, but fist when I copy your code and past, Xcode not recognize the class "LSApplicationWorkspace", I then check online and import in my project the header of the class. But I have only the header. – Sovannarith Jun 25 '13 at 06:30
  • @sovannarith And that's enough. `objc_getClass()` takes care of the rest. If you are messing with undocumented/private stuff, you are expected to 1. run into problems and 2. be able to resolve them on your own. –  Jun 25 '13 at 08:55
  • @H2CO3 I have tried all the possible ways to perform this task but vain and I have to accomplish this task any how,can you please share you personal email ID, want to discuss the issue in detail. – Deepika Lalra Jun 27 '13 at 04:49
  • @H2CO3 I tried all the ways in the discussion, and at last I can install some apps while some other crash immediately at the launch (ex. Facebook messenger, Facebook, CNN for iPhone). Do you have any idea for this crash??? – Vibol Jul 25 '13 at 08:22
  • @VibolTeav Maybe the applications are codesigned (i. e. encrypted, not cracked properly). –  Jul 25 '13 at 10:36
  • @H2CO3 But I try installing those apps via iFunBox and they work fine. – Vibol Jul 26 '13 at 05:50
  • Hiii, i have used your code to install .ipa file in iOS7, it is working fine, but the code for notifying SpringBoard to reload the list of all applications is not working in my case, does you know what may the problem,(if i restart my device, app will show up but i dont want to restart iphone everytime i install ipa file), help will be appreciated – Mehul Thakkar Apr 15 '14 at 11:34
2

You can do this via OTA distribution, see http://help.apple.com/iosdeployment-apps/mac/1.1/#app43ad871e

An example plist can be found here: https://gist.github.com/hramos/774468

Note that you either need the Enterprise Developer Program or collect the UDIDs of your users and include them in your ad-hoc provisioning profile.

ashtom
  • 804
  • 1
  • 8
  • 13
  • 1
    Could i use OTA technique without using Safari but using objective-C code ? – Quang Huynh Feb 22 '13 at 10:28
  • Yes, just call UIApplication#openURL with the itms-services URL. – ashtom Feb 22 '13 at 11:41
  • 1
    Here is an example on how to call the URL: https://github.com/bitstadium/HockeySDK-iOS/blob/develop/Classes/BITUpdateManager.m#L725 The URL to add at the end is the link to the plist file mentioned above. – Kerni Feb 22 '13 at 15:35
  • @Kerni Does Apple restricted the number of apps (100) if we used this method ? – Quang Huynh Feb 25 '13 at 03:13
  • This method does NOT change any of the provisioning profile requirements and signing the apps. So with AdHoc profiles you are still restricted to 100 devices, only enterprise profiles do not have this restriction but may only be used for devices that belong to your company according to the license agreement. – Kerni Feb 25 '13 at 09:36
  • @ashtom when i call UIApplication#openURL with the itms-services URL, my app just quit to the springboard, how do i prevent that ? – Quang Huynh Feb 27 '13 at 03:34
  • This is the expected behavior, but an alert should appear first to ask you if you want to install the app. Are you sure that your plist is correctly formatted? – ashtom Feb 27 '13 at 15:49
  • @ashtom Everything work fine,the app is install when user tap on install button, but how do i tracked whenever user click on "install" or "Cancel", i need to do some actions based on user tap ? – Quang Huynh Mar 15 '13 at 09:04
  • It's not possible to track the button taps. What could work is to check for the applicationDidResignActive / applicationDidBecomeActive events as iOS jumps to the home screen if the user taps "Install". – ashtom Mar 18 '13 at 12:32