Finding OSX name, version, build number

Discussion in 'Mac Programming' started by kamy, Apr 28, 2014.

  1. kamy macrumors member

    Joined:
    Jul 27, 2011
    #1
    I need to find the current Systems
    OS Name
    Version - Major, Minor and Revision
    Build Number

    For example - if i run my code in Mavericks, i should get
    Mac OSX Maverics
    10.9.2
    13C64

    I'm aware that we have an API called 'uname'. This gives me the old Darwin Version number rather than the Marketing Version numbers.
    This API gives me 13.0.0 for Maverics.
    I would rather like to get the Marketing version and I dont get the Build Number properly too.

    So i found that there is a command - sw_vers which gives the following output

    So i decided to use this in my code

    Code:
     system("sw_vers -productVersion > /tmp/MacVersion.txt && sw_vers -buildVersion > /tmp/MacBuild.txt");
            
            MtxString strValues;
            strValues = "";
            
            //Lets read the file
            
            FILEHANDLE hFile = FileOpen ("/tmp/MacVersion.txt", FILEOPEN_READONLY);
            
            if (FILEOPEN_ERROR != hFile)
            {
                ULONG ulBufferSize = FileGetSize ("/tmp/MacVersion.txt");
                
                //Do we have a buffer?
                
                if (ulBufferSize > 0)
                {
                    LPSTR ptrBuffer = (LPSTR) Malloc (ulBufferSize+1);
                    
                    if (ptrBuffer != NULL)
                    {
                        FileRead (hFile, (LPSTR) ptrBuffer, ulBufferSize);
                        ptrBuffer[ulBufferSize] = '\0';
                    
                        strValues.SetTo (ptrBuffer);
                        free (ptrBuffer);
                    }
                }
                
                FileClose (hFile);
            }
            
            MtxVector<MtxString> vecVersions;
            strValues.Split('.', vecVersions, false);
            
            //As the Version is got in format of Major : Minor  : Revision (10.9.2) we split the
            //string and extract the versions
            
            if (vecVersions.Size () == 3)
            {
                ulMajor = vecVersions[0].ToUL ();
                ulMinor = vecVersions[1].ToUL ();
                ulRev = vecVersions[2].ToUL ();
            }
            
            //Lets read the file
            
            FILEHANDLE hFileBuild = FileOpen ("/tmp/MacBuild.txt", FILEOPEN_READONLY);
            
            if (FILEOPEN_ERROR != hFileBuild)
            {
                ULONG ulBufferSizeBuild = FileGetSize ("/tmp/MacBuild.txt");
                
                //Do we have a buffer?
                
                if (ulBufferSizeBuild > 0)
                {
                    LPSTR ptrBuffer = (LPSTR) Malloc (ulBufferSizeBuild+1);
                    
                    if (ptrBuffer != NULL)
                    {
                        FileRead (hFileBuild, (LPSTR) ptrBuffer, ulBufferSizeBuild);
                        ptrBuffer[ulBufferSizeBuild] = '\0';
                        
                        strValues.SetTo (ptrBuffer);
                        free (ptrBuffer);
                    }
                }
                
                FileClose (hFileBuild);
            }
             ....
             ....
             //Get the Build number
             
             //Use a Switch statement to get the MacOSX Name 
            //based on the version. 
    
    The code is self explanatory - i execute the command to get the Version and Build number and read the output from files and then i use a Switch statement to get the OSX Name based on the version.

    I dont like this approach!!!
    We execute a shell command, we read Files, we use bad Switch code to extract the MacOSX Name, which will need constant maintaining every time Apple releases a new OS.

    Can someone guide me on a better way to do this??
    I heard there is an API - Gestalt() But that is deprecated!!!!

    Common Apple we need a simple API to do this!!!
    Thanks in advance!
     
  2. hokan, Apr 28, 2014
    Last edited by a moderator: Apr 28, 2014

    hokan macrumors member

    Joined:
    Mar 18, 2014
    Location:
    Sweden
    #2
    To my knowledge there is currently no good way to do this.
    Apple generally recommends that you instead check for the existence of certain features using e.g. NSClassFromString() and -instancesRespondToSelector: to determine if classes and methods are available.

    There are also some framework version constants (like NSAppKitVersionNumber) that can be checked to get a OS version number as shown below:

    Code:
    if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_0) {
      /* On a 10.0.x or earlier system */
    } else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1) {
      /* On a 10.1 - 10.1.x system */
    } else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_2) {
      /* On a 10.2 - 10.2.x system */
    } else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_3) {
      /* On 10.3 - 10.3.x system */
    } else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_4) {
      /* On a 10.4 - 10.4.x system */
    } else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_5) {
      /* On a 10.5 - 10.5.x system */
    } else {
      /* 10.6 or later system */
    }

    See "SDK Compatibility Guide" https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/cross_development/Introduction/Introduction.html#//apple_ref/doc/uid/10000163i for more details.
     
  3. kamy thread starter macrumors member

    Joined:
    Jul 27, 2011
    #3
    Hi Hokan,

    Thanks for the tips.
    But am using C++ and not Obj C.

    But i shall read the link you have posted anyway.

    Thanks again!
    KammyFC
     
  4. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #4
    First, what is your program doing with this information? If it's looking for features, don't do that. If it's simply presenting to the user, what purpose does that serve?

    Second, you're really doing this the hard way. Why so many open/close cycles? Read the file once, parse it, then pick out the individual parts. Simplicity is a friend to you, and anyone else in the future who sees the code.

    Third, refer to the man page for sw_vers:
    https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/sw_vers.1.html

    Look at the bottom of the page where it tells you which files the command reads from.

    In Objective-C (hence, also in Objective-C++, which can be freely mixed with C++, such as by wrapping it), there are simple ways to read plist files in and turn them into objects:
    https://developer.apple.com/library.../PropertyLists/Introduction/Introduction.html

    So use one of those methods to read the cited file, make a suitable object, then retrieve the strings from the plist object.

    You can discover the names of the objects using this Terminal command:
    Code:
    defaults read /System/Library/CoreServices/SystemVersion
    
    This will show the names and current values. The names are unlikely to change over time (they haven't in the past), although the values certainly will.


    Without doing more research, I don't know of a system-provided way to get the name of the OS version, such as "Mountain Lion" or "Mavericks". Depending on why you need the information in the first place, maybe you should just make a localized strings file in your app, holding an array indexable by version number (e.g. the 9 in "10.9.2" or the 8 in "10.8.4"). That file can be stored as a plist file in the app-bundle, read into an NSArray, then easily referenced to retrieve a name string. It might be a half-dozen lines of Objective-C code, plus creating the plist file in the first place.
     
  5. kamy thread starter macrumors member

    Joined:
    Jul 27, 2011
    #5
    Hi Chown33,

    This is an awesome suggestion, i got to read the Apple Man pages better.

    running the Command shows me the info i need

    So i read the /System/Library/CoreServices/SystemVersion.bundle file using Carbon API's...

    But the problem is -'dictionaryRef' is returning count as '0' elements.

    Code:
     	MtxString strVersion;
            
            
            CFURLRef bundleURL;
            CFBundleRef myBundle;
            
            // Make a CFURLRef from the CFString representation of the
            // bundle’s path.
            bundleURL = CFURLCreateWithFileSystemPath(
                                                      kCFAllocatorDefault,
                                                      CFSTR("/System/Library/CoreServices/SystemVersion.bundle”),
                                                      kCFURLPOSIXPathStyle,
                                                      true );
                    
            // Make a bundle instance using the URLRef.
            myBundle = CFBundleCreate( kCFAllocatorDefault, bundleURL );
                    
            CFStringRef  productBuildString = 0;
            CFStringRef  productVersionString = 0;
            
            CFDictionaryRef dictionaryRef = 0;
            
            dictionaryRef = CFBundleGetInfoDictionary(myBundle);
            
            if(dictionaryRef != NULL)
            {            
                // If we succeeded, look for our property.
                if ( dictionaryRef != NULL ) {
                    productBuildString = (CFStringRef)CFDictionaryGetValue( dictionaryRef,
                                                            CFSTR("ProductBuildVersion") );
                    productVersionString = (CFStringRef)CFDictionaryGetValue( dictionaryRef,
                                                              CFSTR("ProductVersion") );
                }
            }
            
            static char string[32];
            int dictSize = CFDictionaryGetCount(dictionaryRef);
            
            fprintf (pFile,"Dict Count : %d \n",dictSize);
            
            // You can release the URL now.
            CFRelease( bundleURL );
            
            // Use the bundle...
            
            // Release the bundle when done.
            CFRelease( myBundle );
    

    The folder has both the .bundle and the .plist file
    So i guess i can just load the .plist file using an XML parser and then get the values - as a .plist file is an XML file.

    Am getting closer.... let me keep researching!
    Thanks again!

    ----------

    And the reason am doing this is that i need to display the
    MacOSX name, OSX version and Build number in our product. Thus all this madness!
     
  6. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #6
    Go and look inside SystemVersion.bundle, and post what kind of bundle you think it is. What files does it contain? What is the content of those files? Does it have an Info.plist? Does it match the structure of any of the bundle types described here?
    https://developer.apple.com/library...on.html#//apple_ref/doc/uid/10000123i-CH1-SW1

    Then look up exactly what CFBundleGetInfoDictionary() does. Exactly which file does it load from a bundle?


    Not every plist file is an XML file. Even if SystemVersion.plist is currently an XML file, there is no guarantee it will always be one.

    If you're using only CF functions, then use the CF functions that can load plists. Read the section "Reading and Writing Property-List Data" in the "Property-List Programming Guide" I previously linked.


    The Apple menu is always available. The "About This Mac" item can always be chosen from it. At that point, the user can always see the name, version, and build number. The presented dialog will even be localized.

    Saying that your product needs to display this information doesn't explain why this display is necessary. By "necessary", I mean exactly that. If the feature were removed from your product, what fundamental capability would your product lose, that isn't already accomplished by a system capability?

    I'm raising these questions because every line of code written is a line that must be maintained. If a system capability can provide the necessary information to the user without writing any lines of code at all, then that's a less expensive, more reliable, and more adaptable approach. And it takes zero code, zero resources, and zero maintenance.

    I mention maintenance because you noted it in your original post, regarding the OS name. That value is conspicuously absent from SystemVersion.plist. This means that by providing your own code, resources, etc. you will have to update your app when new OS names are shipped, simply because you aren't using the system capability embodied in the Apple menu.
     
  7. kamy thread starter macrumors member

    Joined:
    Jul 27, 2011
    #7
    Solved

    Hi Chown33,

    Thanks for your valuable input.

    I could get the data from the .plist using using the CF API's

    Code:
    //uname() API , which we used earlier to extract the version details, returns the version of the OSX kernel,
            //not the marketing version (e.g. "10.8").  Also the build Number extracted was wrong.
            //Thus we query the - /System/Library/CoreServices/SystemVersion.plist XML file to get the Version Information.
            
            MtxString strVersion;
            MtxString strBuildNumber;
            
            
            // Create a URL specifying the file to hold the XML data.
            
            CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
                                                             CFSTR("/System/Library/CoreServices/SystemVersion.plist"),    // file path name
                                                             kCFURLPOSIXPathStyle,                                         // interpret as POSIX path
                                                             false);                                                       // is it a directory?
    
            CFDataRef resourceData = NULL;
            SInt32 errorCode;
            Boolean status = CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, fileURL, &resourceData, NULL, NULL, &errorCode);
            
            if (!status)
            {
                // Handle the error
                return FALSE;
            }
            
            // Reconstitute the dictionary using the XML data
            
            CFErrorRef myError = NULL;
            CFPropertyListRef propertyList = CFPropertyListCreateWithData(kCFAllocatorDefault, resourceData, kCFPropertyListImmutable, NULL, &myError);
            
            CFTypeID typeID = CFGetTypeID(propertyList);
            
            if (typeID == CFDictionaryGetTypeID())
            {
                //The Property Object is a Dictionary, so extract the version information from the Dictionary
                
                CFStringRef strCFProductBuildVersion;
                strCFProductBuildVersion = CFStringCreateWithCString(NULL, "ProductBuildVersion", kCFStringEncodingASCII);
                
                CFStringRef strCFProductBuildVersionValue = (CFStringRef) CFDictionaryGetValue((CFDictionaryRef)propertyList, strCFProductBuildVersion);
                
                CFStringRef strCFProductVersion;
                strCFProductVersion = CFStringCreateWithCString(NULL, "ProductVersion", kCFStringEncodingASCII);
                
                CFStringRef strCFProductVersionValue = (CFStringRef) CFDictionaryGetValue((CFDictionaryRef)propertyList, strCFProductVersion);
                
                
                const char *bytes;
                bytes = CFStringGetCStringPtr(strCFProductBuildVersionValue, kCFStringEncodingASCII);
                
                if (bytes != NULL)
                {
                    strBuildNumber = bytes;
                }
                
                bytes = CFStringGetCStringPtr(strCFProductVersionValue, kCFStringEncodingASCII);
                
                if (bytes != NULL)
                {
                    strVersion = bytes;
                }
                
                if (strCFProductBuildVersion != NULL)
                {
                    CFRelease(strCFProductBuildVersion);
                }
                if (strCFProductBuildVersionValue != NULL)
                {
                    CFRelease(strCFProductBuildVersionValue);
                }
                if (strCFProductVersion != NULL)
                {
                    CFRelease(strCFProductVersion);
                }
                if (strCFProductVersionValue != NULL)
                {
                    CFRelease(strCFProductVersionValue);
                }
            }
            else
            {
                //PropertyList is not a Hashtable...  We just Bail
                
                return FALSE;
            }
            
            MtxVector<MtxString> vecVersions;
            strVersion.Split('.', vecVersions, false);
            
            //As the Version is got in format of Major : Minor  : Revision (10.9.2) we split the
            //string and extract the versions
            
            if (vecVersions.Size () == 3)
            {
                ulMajor = vecVersions[0].ToUL ();
                ulMinor = vecVersions[1].ToUL ();
                ulRev = vecVersions[2].ToUL ();
            }
           
            //We only support current version + pervious 2 versions of OSX
            //So lets only keep track of names starting from Tiger
            
            if ((ulMajor == 10) && (ulMinor == 4))
            {
                strOS = "Mac OS X Tiger";
            }
            else if ((ulMajor == 10) && (ulMinor == 5))
            {
                strOS = "Mac OS X Leopard";
            }
            else if ((ulMajor == 10) && (ulMinor == 6))
            {
                strOS = "Mac OS X Snow Leopard";
            }
            else if ((ulMajor == 10) && (ulMinor == 7))
            {
                strOS = "Mac OS X Lion";
            }
            else if ((ulMajor == 10) && (ulMinor == 8))
            {
                strOS = "Mac OS X Mountain Lion";
            }
            else if ((ulMajor == 10) && (ulMinor == 9))
            {
                strOS = "Mac OS X Mavericks";
            }
            else
            {
                strOS = "Mac OS X";
            }
    
    
    This helped me a lot.
    Am aware that getting the product name is code that would need periodic maintenance. But we are ok with it for now.

    As to why i would need such code to detect the version info. Ours is a networking scanner product. We have agents on Mac clients which send all kinds of information about the system to Master agents (Linux/Windows).
    Thus the need to get crazy stuff like this :rolleyes:

    Thanks again. I appreciate the inputs
     
  8. briloronmacrumo macrumors 6502

    briloronmacrumo

    Joined:
    Jan 25, 2008
    Location:
    USA
    #8
    The OS version string and build number is easily accessible via:

    Code:
        NSProcessInfo *processInfo = [NSProcessInfo processInfo];
        
        NSArray *myarr = [[processInfo operatingSystemVersionString] componentsSeparatedByString:@" "];
    
        NSLog(@"%@", [myarr objectAtIndex:0]);
        NSLog(@"%@", [myarr objectAtIndex:1]);
        NSLog(@"%@", [myarr objectAtIndex:2]);
        NSLog(@"%@", [myarr objectAtIndex:3]);
    
    A little more parsing might be required depending on the final format needed.
     
  9. hokan macrumors member

    Joined:
    Mar 18, 2014
    Location:
    Sweden
    #9
    Be aware that NSProcessInfo -operatingSystemVersionString docs say the following:

    "This string is human readable, localized, and is appropriate for displaying to the user. This string is not appropriate for parsing."
    - 10.8 Xcode docs​

    This implies that the format could change at any time and more importantly that it could be formatted differently depending on the users language & country setting - so parsing could end up being rather complex.
     
  10. kamy thread starter macrumors member

    Joined:
    Jul 27, 2011
    #10
    Hi,

    The code uses Objective C API's
    But i need to use Core Foundation, APIs.

    Anyway thanks for that suggestion.
    I shall research that.

    KamyFC
     

Share This Page