28

I have written a PC auditing tool many years ago and have been keeping it up to date. One of basic functions is reporting the version of Windows running on the PC being audited for which I have always used the GetVersionEx call.

This works up to and including Windows 8 but is not supported under Windows 10 and indeed Windows 10 returns 8.2 just as windows 8 does. Microsoft do not seem to have introduced anything as a direct replacement suggesting instead that you check for specific features required rather than looking at the OS but for the purpose of the audit I actually want the OS name.

The 'scanner' is a C++ program which must run under non-privileged accounts so I don't think another suggestion I have read - picking up the version of a system DLL such as kernel32.dll will work as these folders are typically not accessible to users.

Any other suggestions/thoughts are most welcome!

user1169502
  • 368
  • 1
  • 5
  • 13
  • For an auditing tool, you should NOT relying on detecting a particular version (10? Which flavor of 10?), but use how the OS describes itself. That way future versions don't break the code. – Ben Voigt Aug 26 '16 at 19:43
  • 1
    Also, non-privileged accounts can most certainly **read** from system DLLs such as `kernel32.dll`. – Ben Voigt Aug 26 '16 at 19:44
  • 1
    You don't detect Windows 10! [Windows 10 detects you!](http://www.networkworld.com/article/2956574/microsoft-subnet/windows-10-privacy-spyware-settings-user-agreement.html) – Ferruccio Aug 26 '16 at 19:45
  • Voting to re-open because the goals are different (as is the programming language). Detecting Windows 10 is different than getting the Windows version number (mild shock). (Admittedly, some of the obstacles are the same or, at least, related.) Also, the other question is geared toward C#. – Adrian McCarthy Apr 16 '18 at 17:31

7 Answers7

17

Starting in Windows 8.1, GetVersion() and GetVersionEx() are subject to application manifestation:

With the release of Windows 8.1, the behavior of the GetVersionEx API has changed in the value it will return for the operating system version. The value returned by the GetVersionEx function now depends on how the application is manifested.

Applications not manifested for Windows 8.1 or Windows 10 will return the Windows 8 OS version value (6.2). Once an application is manifested for a given operating system version, GetVersionEx will always return the version that the application is manifested for in future releases. To manifest your applications for Windows 8.1 or Windows 10, refer to Targeting your application for Windows.

The newer Version Helper functions are simply wrappers for VerifyVersionInfo(). Starting in Windows 10, it is now subject to manifestation as well:

Windows 10: VerifyVersionInfo returns false when called by applications that do not have a compatibility manifest for Windows 8.1 or Windows 10 if the lpVersionInfo parameter is set so that it specifies Windows 8.1 or Windows 10, even when the current operating system version is Windows 8.1 or Windows 10. Specifically, VerifyVersionInfo has the following behavior:

  • If the application has no manifest, VerifyVersionInfo behaves as if the operation system version is Windows 8 (6.2).
  • If the application has a manifest that contains the GUID that corresponds to Windows 8.1, VerifyVersionInfo behaves as if the operation system version is Windows 8.1 (6.3).
  • If the application has a manifest that contains the GUID that corresponds to Windows 10, VerifyVersionInfo behaves as if the operation system version is Windows 10 (10.0).

The Version Helper functions use the VerifyVersionInfo function, so the behavior IsWindows8Point1OrGreater and IsWindows10OrGreater are similarly affected by the presence and content of the manifest.

To manifest your applications for Windows 8.1 or Windows 10, see Targeting your application for Windows.

To get the true OS version regardless of manifestation use RtlGetVersion(), NetServerGetInfo(), or NetWkstaGetInfo() instead. They all report an accurate OS version and are not subject to manifestation (yet?).

(Microsoft used to suggest querying the file version of a system DLL, but they stopped recommending that when Windows didn't update system DLL versions to match.)

Nathan Kidd
  • 2,919
  • 21
  • 22
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I did not understand "VerQueryValue to obtain the \\StringFileInfo\\\\ProductVersion subblock of the file version information." of your answer. What does "\\StringFileInfo\\\\ProductVersion" represent? what does subblock of the file version information mean? – Sahil Singh Feb 07 '18 at 09:59
  • @SahilSingh that information is covered in the `VerQueryValue()` documentation, and there are tons of examples/tutorials online showing how to use `VerQueryValue()`. Please take the time to do some research before asking questions. – Remy Lebeau Feb 07 '18 at 16:55
16

GetVersion and GetVersionEx were superseded by various version helper functions. The one you want is IsWindows10OrGreater. They can be found in VersionHelpers.h.

IsWindows10OrGreater is only available in the latest SDK/Visual Studio 2015. You can use IsWindowsVersionOrGreater in the general case however. For example on my 7 box I get TRUE for IsWindowsVersionOrGreater(6, 0, 0).

Remember that the parameters this function takes relate to Windows build number and NOT marketing name. So Windows 8 is build 6.2. Windows 7 is 6.0 etc.

Robinson
  • 9,666
  • 16
  • 71
  • 115
  • 11
    `BOOL WINAPI IsWindows10OrGreater(void);`. Wow that is great API design. – M.M Aug 20 '15 at 10:39
  • 3
    They're just convenience functions. There's a general *IsWindowsVersionOrGreater* function. – Robinson Aug 20 '15 at 10:45
  • 3
    trouble is they are not available in earlier versions of Windows which the scanner has to run on. Can I dynamically load them if available? – user1169502 Aug 20 '15 at 12:31
  • thanks - I need to download and instal the Windows 8.1 SDK before I can use the helpers. hopefully they will work with VS 2008 as I have to build the executable with VS 2008 as it won't run on earlier versions of Windows if I build under VS 2010 or 2012! – user1169502 Aug 20 '15 at 13:34
  • Oh yes, you need the Platform SDK. I can't do a repo for 2008 unfortunately (uninstalled that about six months ago). – Robinson Aug 20 '15 at 14:09
  • Seems complicated/difficult/no idea how to implement this so that my program will build in VS 2008 under iwndows 7. The folder containing the version functions is not part of the include path (easily sorted) but I have another issue with the NTDDI_VERSION directive as it needs to be set to NTDDI_WINXP or higher and just not sure where to go with this... – user1169502 Aug 20 '15 at 16:48
  • 1
    I don't understand the 'obviously' in your post about IsWindows10OrGreater only being available in w10, IsWindows8Point1OrGreater] which is the previous version is available on w 2000 platform, just like the others, m$ only seems to want to cause trouble to it's devs. – SomeNickName Oct 22 '15 at 15:11
  • I think it depends on your version of *versionhelpers.h*. My Visual Studio 2013, running on Windows 10, certainly doesn't have it. I think 2015/the latest SDK does though. – Robinson Oct 22 '15 at 16:14
  • 11
    It appears that ISWindows10OrGreater (or it's more generic variant, IsWindowsVersionOrGreater) will not reliably indicate the OS. The behaviour now depends on how the application "is manifested", meaning even on Windows 10, an application can tell you it's on Windows 8.1 or Windows 10. If you haven't targeted your application to Windows 10, it'll always tell you Windows 8.1 – TheDuke Nov 12 '15 at 22:02
  • 5
    The `IsWindowsXXOrGreater()` functions are not actual functions at all, they are just macro wrappers for the `VerifyVersionInfo()` functions, which is now subject to manifestation starting in Windows 10. – Remy Lebeau Aug 26 '16 at 19:08
  • My answer addresses the key point to all this, as stated by @TheDuke – BuvinJ Aug 26 '16 at 19:10
  • @BuvinJ: your answer depends on the application having a manifest, so reliable OS detection is outside of the code's control. I just posted an answer that mentions several ways to get the true OS version without relying on a manifest, and all of them have been tested as working on Windows 10. – Remy Lebeau Aug 26 '16 at 19:19
  • 1
    Cool. I think you can pull this info from the registry too. It's really absurd MS changed something like this! – BuvinJ Aug 26 '16 at 19:34
15

Use the following function:

double getSysOpType()
{
    double ret = 0.0;
    NTSTATUS(WINAPI *RtlGetVersion)(LPOSVERSIONINFOEXW);
    OSVERSIONINFOEXW osInfo;

    *(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandleA("ntdll"), "RtlGetVersion");

    if (NULL != RtlGetVersion)
    {
        osInfo.dwOSVersionInfoSize = sizeof(osInfo);
        RtlGetVersion(&osInfo);
        ret = (double)osInfo.dwMajorVersion;
    }
    return ret;
}

It will return the Windows version as a double (7, 8, 8.1, 10).

Michael Haephrati
  • 3,660
  • 1
  • 33
  • 56
  • This seems to be the best solution and deserves more upvotes :) – BullyWiiPlaza Jun 13 '19 at 13:01
  • Thanks! We have been using this approach for years and I does work perfectly! – Michael Haephrati Jun 13 '19 at 18:06
  • @Bonfire #include – Alexandru Dicu Jan 28 '20 at 22:37
  • This doesn't work for me... It throws errors like "RtlGetVersion' was not declared in this scope" – Diogenis Siganos Jan 31 '20 at 15:01
  • @Bonfire, I tested it and it works perfectly. If a function isn't declared, add the necessary include file. Also please don't use the phrase "doesn't work" when you are not able to build the code, in such case write that you can't compile. These are 2 different issues. Only if you successfully built and ran the code and it didn't detect the current OS, then you can claim it doesn't work. – Michael Haephrati Jan 31 '20 at 19:54
  • `dwMajorVersion` is not of type double. – ssbssa Sep 14 '20 at 11:45
  • fixed - changed returned variable to double – Michael Haephrati Sep 14 '20 at 17:32
  • https://stackoverflow.com/a/62371184/1923561 Based on Michael Haephrati's answer, I made adjustments to my code. – 赫敏璋 Jan 12 '21 at 01:39
  • 5
    *"It will return the Windows version as a double (7, 8, 8.1, 10)."* - That's incorrect. Constructing a floating point value from a `DWORD` will not ever produce a fractional number. And if it did, there would be no way to accurately represent 8.1. Though that is all moot, since this function will not ever return 8 anyway. It returns 10 for Windows 10, and [6 for all versions of Windows from Vista up to 8.1](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexw). – IInspectable Feb 20 '21 at 07:38
  • Why would you cast a DWORD to double? Why? – raymai97 Mar 11 '23 at 01:29
5

2021-01-12 https://stackoverflow.com/a/52122386/1923561 Based on Michael Haephrati's answer, I made adjustments to my code.

enum WindowsOS{
   NotFind,
   Win2000,
   WinXP,
   WinVista,
   Win7,
   Win8,
   Win10
};

WindowsOS GetOsVersionQuick()
{
   using namespace std;
   double ret = 0.0;
   NTSTATUS(WINAPI *RtlGetVersion)(LPOSVERSIONINFOEXW);
   OSVERSIONINFOEXW osInfo;

   *(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandleA("ntdll"), 
   "RtlGetVersion");

   if (NULL != RtlGetVersion)
   {
      osInfo.dwOSVersionInfoSize = sizeof(osInfo);
      RtlGetVersion(&osInfo);
      ret = (double)osInfo.dwMajorVersion;
   }

   if (osInfo.dwMajorVersion == 10 && osInfo.dwMinorVersion == 0)
   {
      cout << "this is windows 10\n";
      return Win10;
   }
   else if (osInfo.dwMajorVersion == 6 && osInfo.dwMinorVersion == 3)
   {
      cout << "this is windows 8.1\n";
      return Win8;
   }
   else if (osInfo.dwMajorVersion == 6 && osInfo.dwMinorVersion == 2)
   {
      cout << "this is windows 8\n";
      return Win8;
   }
   else if (osInfo.dwMajorVersion == 6 && osInfo.dwMinorVersion == 1)
   {
      cout << "this is windows 7 or Windows Server 2008 R2\n";
      return Win7;
   }

   return NotFind;
}

2020-06-14

#include <iostream>
#include <windows.h>
#pragma comment(lib, "Version.lib" )

BOOL GetOsVersion()
{
    wchar_t path[200] = L"C:\\Windows\\System32\\kernel32.dll";
    DWORD dwDummy;
    DWORD dwFVISize = GetFileVersionInfoSize(path, &dwDummy);
    LPBYTE lpVersionInfo = new BYTE[dwFVISize];
    if (GetFileVersionInfo(path, 0, dwFVISize, lpVersionInfo) == 0)
    {
        return FALSE;
    }

    UINT uLen;
    VS_FIXEDFILEINFO* lpFfi;
    BOOL bVer = VerQueryValue(lpVersionInfo, L"\\", (LPVOID*)&lpFfi, &uLen);

    if (!bVer || uLen == 0)
    {
        return FALSE;
    }
    DWORD dwProductVersionMS = lpFfi->dwProductVersionMS;
    if (HIWORD(dwProductVersionMS) == 10 && LOWORD(dwProductVersionMS) == 0)
    {
        cout << "this is windows 10\n";
    }
    else if (HIWORD(dwProductVersionMS) == 6 && LOWORD(dwProductVersionMS) == 3)
    {
        cout << "this is windows 8.1\n";
    }
    else if (HIWORD(dwProductVersionMS) == 6 && LOWORD(dwProductVersionMS) == 2)
    {
        cout << "this is windows 8\n";
    }
    else if (HIWORD(dwProductVersionMS) == 6 && LOWORD(dwProductVersionMS) == 1)
    {
        cout << "this is windows 7 or Windows Server 2008 R2\n";
    }
    else if (HIWORD(dwProductVersionMS) == 6 && LOWORD(dwProductVersionMS) == 0)
    {
        cout << "this is windows Vista or Windows Server 2008\n";
    }
    else if (HIWORD(dwProductVersionMS) == 5 && LOWORD(dwProductVersionMS) == 2)
    {
        cout << "this is windows Server 2003\n";
    }
    else if (HIWORD(dwProductVersionMS) == 5 && LOWORD(dwProductVersionMS) == 1)
    {
        cout << "this is windows Server XP\n";
    }
    else if (HIWORD(dwProductVersionMS) == 5 && LOWORD(dwProductVersionMS) == 0)
    {
        cout << "this is windows 2000\n";
    }
    //else if (lpFfi->dwFileVersionMS == 4 && lpFfi->dwFileVersionLS == 90)
    //{
    //    cout << "this is windows  Me\n";
    //}
    //else if (lpFfi->dwFileVersionMS == 4 && lpFfi->dwFileVersionLS == 10)
    //{
    //    cout << "this is windows  98\n";
    //}
    //else if (lpFfi->dwFileVersionMS == 4 && lpFfi->dwFileVersionLS == 0)
    //{
    //    cout << "this is windows  95\n";
    //}
    return TRUE;
}

After testing the code used to detect win10.

I speculate that this api error, IsWindows10OrGreater, is because the wrong FileVersionMS version is set for kernel32.dll. Use ProductVersionMS version query to get it normally.

https://learn.microsoft.com/en-us/windows/win32/api/versionhelpers/nf-versionhelpers-iswindows10orgreater

Hope it could help everyone!

赫敏璋
  • 81
  • 1
  • 4
2

I needed this to work on an older version of the VS compiler, and more over within a Qt framework. Here's how I accomplished that.

Add this file GetWinVersion.h to your Qt project:

#ifndef GETWINVERSION
#define GETWINVERSION

#include <QtGlobal>

#ifdef Q_OS_WIN

#include <windows.h>
#include <stdio.h>

float GetWinVersion()
{
    OSVERSIONINFO osvi;
    ZeroMemory( &osvi, sizeof(OSVERSIONINFO) );
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    return GetVersionEx( &osvi ) ?
           (float)osvi.dwMajorVersion +
           ((float)osvi.dwMinorVersion/10) :
           0.0 ;
}

#endif //Q_OS_WIN

#endif // GETWINVERSION

Add the required linkage in your pro or pri qmake file:

msvc: LIBS += -lKernel32

Implement the helper function like so (note SystemInfo used here is a custom class of mine, but you get the idea...):

#include "GetWinVersion.h"

SystemInfo info;

#ifdef Q_OS_WIN
    info.setPlatform( SystemInfo::WINDOWS );
    switch(QSysInfo::windowsVersion())
    {
    case QSysInfo::WV_32s:        info.setOsName( L"3.1" );     info.setOsVersion( 3.1 ); break;
    case QSysInfo::WV_95:         info.setOsName( L"95" );      info.setOsVersion( 4.0 ); break;
    case QSysInfo::WV_98:         info.setOsName( L"98" );      info.setOsVersion( 4.1 ); break;
    case QSysInfo::WV_Me:         info.setOsName( L"Me" );      info.setOsVersion( 4.9 ); break;
    case QSysInfo::WV_NT:         info.setOsName( L"NT" );      info.setOsVersion( 4.0 ); break;
    case QSysInfo::WV_2000:       info.setOsName( L"2000" );    info.setOsVersion( 5.0 ); break;
    case QSysInfo::WV_XP:         info.setOsName( L"XP" );      info.setOsVersion( 5.1 ); break;
    case QSysInfo::WV_2003:       info.setOsName( L"2003" );    info.setOsVersion( 5.2 ); break;  // Windows Server 2003, Windows Server 2003 R2, Windows Home Server, Windows XP Professional x64 Edition
    case QSysInfo::WV_VISTA:      info.setOsName( L"Vista" );   info.setOsVersion( 6.0 ); break;  // Windows Vista, Windows Server 2008
    case QSysInfo::WV_WINDOWS7:   info.setOsName( L"7" );       info.setOsVersion( 6.1 ); break;  // Windows 7, Windows Server 2008 R2
    case QSysInfo::WV_WINDOWS8:   info.setOsName( L"8" );       info.setOsVersion( 6.2 ); break;  // Windows 8, Windows Server 2012
  // These cases are never reached due to Windows api changes
  // As of Qt 5.5, this not accounted for by QSysInfo::windowsVersion()
  //case QSysInfo::WV_WINDOWS8_1: info.setOsName( L"8.1" );     info.setOsVersion( 6.3 ); break;  // Windows 8.1, Windows Server 2012 R2
  //case QSysInfo::WV_WINDOWS10:  info.setOsName( L"10" );      info.setOsVersion( 10.0 ); break; // Windows 10, Windows Server 2016
    default:
        // On Windows 8.1 & 10, this will only work when the exe
        // contains a manifest which targets the specific OS's
        // you wish to detect.  Else 6.2 (ie. Win 8.0 is returned)
        info.setOsVersion( GetWinVersion() );
        if(      info.osVersion() == 6.3f )  // Windows 8.1, Windows Server 2012 R2
            info.setOsName( L"8.1" );
        else if( info.osVersion() == 10.0f ) // Windows 10, Windows Server 2016
            info.setOsName( L"10" );
        else
            info.setOsName( L"UNKNOWN" );
    }
    info.setOsBits( IsWow64() ? 64 : 32 );
#else
...

Now here's the real key. You need to attach a manifest file to your exe which will "target" the recent Windows versions, else you can't detect them (see the MS docs: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451%28v=vs.85%29.aspx). Here's an example manifest to do this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity 
        name="MyOrg.MyDept.MyAppName" 
        version="1.0.0.0" 
        processorArchitecture="x86" 
        type="win32" />
    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> 
        <application> 
            <!-- Windows 10 --> 
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
            <!-- Windows 8.1 -->
            <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
            <!-- Windows 8 -->
            <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
            <!-- Windows 7 -->
            <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>      
            <!-- Windows Vista -->
            <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>          
        </application> 
    </compatibility>
</assembly>

And here's some batch to attach the manifest:

set exeFile=MyApp.exe
set manifestFile=MyApp.manifest
set manifestExe=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\x64\mt.exe

"%manifestExe%" -manifest "%manifestFile%" -outputresource:"%exeFile%"

In theory, you can use qmake to run that last bit attaching the manifest. I didn't have luck with the examples I found, and just "cheated" with this batch for now...

BuvinJ
  • 10,221
  • 5
  • 83
  • 96
2

Do not use VersionHelpers.h! It's buggy!

It ignores the user's application compatibility settings.

Instead, use the older Kernel32.dll functions like GetVersion, e.g.:

bool IsWindowsVersionOrGreater(unsigned short version)
{
    return _byteswap_ushort((unsigned short)GetVersion()) >= version;
}

// Usage: IsWindowsVersionOrGreater(_WIN32_WINNT_WINTHRESHOLD)

user541686
  • 205,094
  • 128
  • 528
  • 886
0

FWIW, the LibreOffice project gives the version string via getOSVersion()

OUString WinSalInstance::getOSVersion()
{
    OUStringBuffer aVer(50); // capacity for string like "Windows 6.1 Service Pack 1 build 7601"
    aVer.append("Windows ");
    // GetVersion(Ex) and VersionHelpers (based on VerifyVersionInfo) API are
    // subject to manifest-based behavior since Windows 8.1, so give wrong results.
    // Another approach would be to use NetWkstaGetInfo, but that has some small
    // reported delays (some milliseconds), and might get slower in domains with
    // poor network connections.
    // So go with a solution described at https://web.archive.org/web/20090228100958/http://msdn.microsoft.com/en-us/library/ms724429.aspx
    bool bHaveVerFromKernel32 = false;
    if (HMODULE h_kernel32 = GetModuleHandleW(L"kernel32.dll"))
    {
        wchar_t szPath[MAX_PATH];
        DWORD dwCount = GetModuleFileNameW(h_kernel32, szPath, SAL_N_ELEMENTS(szPath));
        if (dwCount != 0 && dwCount < SAL_N_ELEMENTS(szPath))
        {
            dwCount = GetFileVersionInfoSizeW(szPath, nullptr);
            if (dwCount != 0)
            {
                std::unique_ptr<char[]> ver(new char[dwCount]);
                if (GetFileVersionInfoW(szPath, 0, dwCount, ver.get()) != FALSE)
                {
                    void* pBlock = nullptr;
                    UINT dwBlockSz = 0;
                    if (VerQueryValueW(ver.get(), L"\\", &pBlock, &dwBlockSz) != FALSE && dwBlockSz >= sizeof(VS_FIXEDFILEINFO))
                    {
                        VS_FIXEDFILEINFO* vi1 = static_cast<VS_FIXEDFILEINFO*>(pBlock);
                        aVer.append(OUString::number(HIWORD(vi1->dwProductVersionMS)) + "."
                                    + OUString::number(LOWORD(vi1->dwProductVersionMS)));
                        bHaveVerFromKernel32 = true;
                    }
                }
            }
        }
    }
    // Now use RtlGetVersion (which is not subject to deprecation for GetVersion(Ex) API)
    // to get build number and SP info
    bool bHaveVerFromRtlGetVersion = false;
    if (HMODULE h_ntdll = GetModuleHandleW(L"ntdll.dll"))
    {
        if (auto RtlGetVersion
            = reinterpret_cast<RtlGetVersion_t>(GetProcAddress(h_ntdll, "RtlGetVersion")))
        {
            RTL_OSVERSIONINFOW vi2{}; // initialize with zeroes - a better alternative to memset
            vi2.dwOSVersionInfoSize = sizeof(vi2);
            if (STATUS_SUCCESS == RtlGetVersion(&vi2))
            {
                if (!bHaveVerFromKernel32) // we failed above; let's hope this would be useful
                    aVer.append(OUString::number(vi2.dwMajorVersion) + "."
                                + OUString::number(vi2.dwMinorVersion));
                aVer.append(" ");
                if (vi2.szCSDVersion[0])
                    aVer.append(OUString::Concat(o3tl::toU(vi2.szCSDVersion)) + " ");
                aVer.append("Build " + OUString::number(vi2.dwBuildNumber));
                bHaveVerFromRtlGetVersion = true;
            }
        }
    }
    if (!bHaveVerFromKernel32 && !bHaveVerFromRtlGetVersion)
        aVer.append("unknown");
    return aVer.makeStringAndClear();
}
Chris Sherlock
  • 3,112
  • 2
  • 13
  • 9