Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

kamy

macrumors member
Original poster
Jul 27, 2011
44
0
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
mac-kmallick-01:SysPortLib kmallick$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.9.2
BuildVersion: 13C64


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!
 
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.
 
Last edited by a moderator:
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.

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
 
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.
 
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.

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
mac-kmallick-01:head kmallick$ defaults read /System/Library/CoreServices/SystemVersion
{
ProductBuildVersion = 13C64;
ProductCopyright = "1983-2014 Apple Inc.";
ProductName = "Mac OS X";
ProductUserVisibleVersion = "10.9.2";
ProductVersion = "10.9.2";
}


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
drwxr-xr-x 35 root wheel 1190 Oct 17 2013 SystemVersion.bundle
-r--r--r-- 1 root wheel 478 Feb 18 03:46 SystemVersion.plist

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!
 
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?


The folder has both the .bundle and the .plist file
Code:
drwxr-xr-x 35 root wheel 1190 Oct 17 2013 SystemVersion.bundle
-r--r--r-- 1 root wheel 478 Feb 18 03:46 SystemVersion.plist
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.
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.


And the reason am doing this is that i need to display the
MacOSX name, OSX version and Build number in our product.
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.
 
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
 
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.
 
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.
 
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
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.