Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Determining Windows Version

Status
Not open for further replies.

Glenn9999

Programmer
Jun 19, 2004
2,312
US
Referring to Thread: thread102-1568664
and FAQ: faq102-3232

It seems with the different windows flavors that are out there that it is difficult to get a good comprehensive version identification. That can be from the logic, the number of them (hard to test all of them!) or for the fact it's so hard to test most of them. These large number of flavors makes the code very extensive as well.

I did some research and did some initial coding. The question is always how accurate it is.

Below is code relating to generic version identification.
Code:
unit winver;
  { windows versioning unit.  Generic versioning only. }

  interface
    uses windows;
   const
      { suite identifiers }
      VER_SUITE_WH_SERVER = $8000;
      { product type identifiers }
      VER_NT_WORKSTATION = $01;
      { processor identifiers }
      PROCESSOR_ARCHITECTURE_AMD64 = 9;

      SM_SERVERR2 = 89;
    type
      TOSVersionInfoExA = record
        dwOSVersionInfoSize: DWORD;
        dwMajorVersion: DWORD;
        dwMinorVersion: DWORD;
        dwBuildNumber: DWORD;
        dwPlatformId: DWORD;
        szCSDVersion: array[0..127] of AnsiChar; { Maintenance string for PSS usage }
        wServicePackMajor: Word;
        wServicePackMinor: Word;
        wSuiteMask: Word;
        wProductType: Byte;
        wReserved: byte;
      end;
      TOSVersionInfoExW = record
        dwOSVersionInfoSize: DWORD;
        dwMajorVersion: DWORD;
        dwMinorVersion: DWORD;
        dwBuildNumber: DWORD;
        dwPlatformId: DWORD;
        szCSDVersion: array[0..127] of WideChar; { Maintenance string for PSS usage }
        wServicePackMajor: Word;
        wServicePackMinor: Word;
        wSuiteMask: Word;
        wProductType: Byte;
        wReserved: byte;
      end;
      TOSVersionInfoEx = TOSVersionInfoExA;

    TWinVersion = (wvUnknown, wvw32s, wv95, wv95OSR2, wv98, wv98SE, wvNT,
                wvME, wv2000, wvXP, wvHomeServer, wvXP64, wv2003, wv2003_r2,
                wvVista, wv2008, wv7, wv2008_R2);

  function GetExVersionExA(var lpVersionInformation: TOSVersionInfoExA): boolean; stdcall;
  function GetExVersionExW(var lpVersionInformation: TOSVersionInfoExW): boolean; stdcall;
  procedure GetWinVersion(var inver: TWinVersion);

  implementation

    var
      SysInfo: TSystemInfo;

  function GetExVersionExA(var lpVersionInformation: TOSVersionInfoExA): boolean; stdcall;
      external 'Kernel32.dll' name 'GetVersionExA';
  function GetExVersionExW(var lpVersionInformation: TOSVersionInfoExW): boolean; stdcall;
      external 'Kernel32.dll' name 'GetVersionExW';

  procedure Win9XHandle(ovinfo: TOSVersionInfoExA; var inver: TWinVersion);
    { handle logic for determining version of Win9X systems }
    begin
      if OvInfo.dwMajorVersion = 4 then
        case OvInfo.dwMinorVersion of
          0:  begin
                if OvInfo.szCSDVersion[1] in ['B', 'C'] then
                  inver := wv95OSR2
                else
                  inver := wv95;
              end;
          10: begin
                if OvInfo.szCSDVersion[1] = 'A' then
                  inver := Wv98SE
                else
                  inver := wv98;
              end;
          90: inver := wvME;
        end;
    end;

  procedure Win2003Handle(ovinfo: TOSVersionInfoExA; var inver: TWinVersion);
    { handle code for differentiating Win 2003 class OSes }
    begin
      if ovinfo.wSuiteMask = VER_SUITE_WH_SERVER then
        inver := wvHomeServer
      else
        begin
          if (ovinfo.wProductType = VER_NT_WORKSTATION) and
             (SysInfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64) then
             inver := wvXP64
          else
             if GetSystemMetrics(SM_SERVERR2) <> 0 then
               inver := wv2003_R2
             else
               inver := wv2003;
        end;
    end;

  procedure WinNTHandle(ovinfo: TOSVersionInfoExA; var inver: TWinVersion);
    { handle logic for determining version of WinNT systems }
    begin
      case ovinfo.dwMajorVersion of
        3: inver := wvNT;
        4: inver := wvNT;
        5: case ovinfo.dwMinorVersion of
              0: inver := wv2000;
              1: inver := wvXP;
              2: Win2003Handle(ovinfo, inver);
           end;
        6: case ovinfo.dwMinorVersion of
              0: if ovinfo.wProductType = VER_NT_WORKSTATION then
                    inver := wvVista
                 else
                    inver := wv2008;
              1: if ovinfo.wProductType = VER_NT_WORKSTATION then
                    inver := wv7
                 else
                    inver := wv2008_R2;
           end;
      end;
    end;

  procedure GetWinVersion(var inver: TWinVersion);
    { main versioning routine }
    var
      OsVerInfo: TOSVersionInfoExA;
    begin
      inver := wvUnknown;
      OSVerInfo.dwOsVersionInfoSize := Sizeof(TOsVersionInfoExA);
      if GetExVersionExA(OsVerInfo) then
        case osVerInfo.dwPlatformId of
          VER_PLATFORM_WIN32_NT: WinNTHandle(OSVerInfo, inver);
          VER_PLATFORM_WIN32_WINDOWS: Win9xHandle(OsVerInfo, inver);
          VER_PLATFORM_WIN32S: inver := wvw32s;
        end;
    end;

initialization
  GetSystemInfo(SysInfo);
finalization
end.

I also came up with some more specific version code, eg. "Windows XP Professional" instead of "Windows XP". This relates to things before Vista (can't test Vista or W7, it uses a newer API call to differentiate those).

The code I have tests right on everything I have access to, but hopefully some efforts can be made to get something that is known to work right in all cases.

I'm waiting for the white paper entitled "Finding Employment in the Era of Occupational Irrelevancy
 
(if there is interest, I can post the specific version code when I have it done). I'm mainly asking if it can be verified that the code works on those others, before any of it gets FAQed.

I'm waiting for the white paper entitled "Finding Employment in the Era of Occupational Irrelevancy
 
How can I test this for you? I'm running Vista 64x Home Premium.
 
Your unit could use some user-friendliness. I'm sure I could sort this out but I'd rather use YOUR method, should you choose to add it. :)
Let me demo what I mean...
Code:
program WinVer01;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  winver in 'winver.pas';

var
  inver: TWinVersion;
  s: string;
begin
  GetWinVersion(inver);
  //
  s:= inver;   //NO STRING CONVERSION?!?
  writeln(s);  //fails as is...
  //
  writeln('press <enter> to close...');
  readln;
end.
BTW: I like the way Delphi-IDE displays win-ver when you press 'delphi.menu>help>about'


Roo
Delphi Rules!
 
Glenn,

This page describes anything you need to know:



oh, and delphi has some variables built-in:

Code:
{$IFDEF MSWINDOWS}
{ Win32 platform identifier.  This will be one of the following values:

    VER_PLATFORM_WIN32s
    VER_PLATFORM_WIN32_WINDOWS
    VER_PLATFORM_WIN32_NT

  See WINDOWS.PAS for the numerical values. }

  Win32Platform: Integer = 0;

{ Win32 OS version information -

  see TOSVersionInfo.dwMajorVersion/dwMinorVersion/dwBuildNumber }

  Win32MajorVersion: Integer = 0;
  Win32MinorVersion: Integer = 0;
  Win32BuildNumber: Integer = 0;

{ Win32 OS extra version info string -

  see TOSVersionInfo.szCSDVersion }

  Win32CSDVersion: string = '';

you can find those in the SysUtils unit

/Daddy

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
@Roo: that is not too hard:

Code:
TWinVersion = (wvUnknown, wvw32s, wv95, wv95OSR2, wv98, wv98SE, wvNT, wvME, wv2000, wvXP, wvHomeServer, wvXP64, wv2003, wv2003_r2, wvVista, wv2008, wv7, wv2008_R2);

const TWinVersionStrings :
       array[wvUnknown..wv2008_R2] of string[20] =
                     ('unknown',
                      '3.1',
                      '95',
                      '95 OSR2',
                      '98',
                      '98 SE',
                      'NT',
                      'ME',
                      '2000',
                      'XP',
                      ....

I would opt for a finer grained TWinVersion 

like wvNT351 and wvNT4 instead of wvNT

/Daddy

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
DjangMan: To have what I'm looking to do for Vista and above requires calling GetProductInfo ( Since I do not have access to Vista, I can't personally code/test that.

As far as other things that I have questions on, it's mainly the issue of detecting a 32-bit OS versus a 64-bit OS. I already have this code in what I'm working on (and it works fine for me - 32 bit true, 64 bit false), but I generally get a bit paranoid when I can't see something works in the other direction, as is all this stuff.

The nearest best thing I can figure is to do something like:

Code:
const
    { processor identifiers }
      PROCESSOR_ARCHITECTURE_INTEL = 0;
      PROCESSOR_ARCHITECTURE_AMD64 = 9;
      PROCESSOR_ARCHITECTURE_IA64 = 6;
      PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10;

 function is32bitOS: boolean;
    var
      SysInfo: TSystemInfo;
    begin
      GetSystemInfo(SysInfo);
      Result := (
           (Sysinfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL)
        or (Sysinfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_IA32_ON_WIN64)
        );
    end;

  function is64bitOS: boolean;
    var
      SysInfo: TSystemInfo;
    begin
      GetSystemInfo(SysInfo);
      Result := (
           (Sysinfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64)
        or (Sysinfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_IA64)
        );
    end;

whosrdaddy: I'm well aware of that page and consulted it much on this "specific OS" code. There are still holes as I've found out - I still need to fit XP Media Center, XP Starter, and XP Tablet into the specific OS code somewhere to make it as complete as possible.

As has been noted, much of the difficulty for doing versioning checks is to make sure no OS has been missed, and that the proper checks for each OS have been done. MS has definitely made it hard with the number of variations of OS product they have put out (26 between NT4 & XP).

I figure at the very least it wouldn't hurt to get the information out there.

As for the Sysutils values, you should note there are other values that are used on the GetVersionEx call besides the major and minor version numbers and platform (it had to be rewritten to get those).

I'm waiting for the white paper entitled "Finding Employment in the Era of Occupational Irrelevancy
 
hey daddy - not_quote "..sure I could sort it out" /not_quote and yours does...
lol
Somewhere I have one I wrote that did all that BUT pre-XP. My little app calculated ans reported the actual cpu speed based on ver results. My plan is to contribute my code, but for right now, it's complex enough :)


Roo
Delphi Rules!
 
Here's the specific OS code. It should only work for NT4 - XP, but there's a little bit of look in to Vista Home and Windows Server 2008.

Again I don't know how well any of this works or if something got forgotten (which is really why I'm not looking to FAQ it at this time). But it works on everything I got access to (including the standard XP compatibility layers).

As was noted above, if you want the same idea for Vista and above, you'll have to call GetProductInfo.

Code:
unit prodver;
  { specific windows versioning unit, lot of logic from Marius Bancila
    codeguru post in C++ of May 15, 2009 }

  interface
    uses windows;
   const
      { suite identifiers }
      VER_SUITE_BACKOFFICE = $04;
      VER_SUITE_BLADE = $400;
      VER_SUITE_COMPUTE_SERVER = $4000;
      VER_SUITE_DATACENTER = $80;
      VER_SUITE_ENTERPRISE = $02;
      VER_SUITE_EMBEDDEDNT = $40;
      VER_SUITE_PERSONAL = $200;
      VER_SUITE_SINGLEUSERTS = $100;
      VER_SUITE_SMALLBUSINESS = $01;
      VER_SUITE_SMALLBUSINESS_RESTRICTED = $20;
      VER_SUITE_STORAGE_SERVER = $2000;
      VER_SUITE_TERMINAL = $10;
      VER_SUITE_WH_SERVER = $8000;
      { product type identifiers }
      VER_NT_DOMAIN_CONTROLLER = $02;
      VER_NT_SERVER = $03;
      VER_NT_WORKSTATION = $01;
      { processor identifiers }
      PROCESSOR_ARCHITECTURE_INTEL = 0;
      PROCESSOR_ARCHITECTURE_AMD64 = 9;
      PROCESSOR_ARCHITECTURE_IA64 = 6;
      PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10;

      SM_TABLETPC = 86;
      SM_MEDIACENTER = 87;
      SM_STARTER = 88;
      SM_SERVERR2 = 89;
    type
      TOSVersionInfoExA = record
        dwOSVersionInfoSize: DWORD;
        dwMajorVersion: DWORD;
        dwMinorVersion: DWORD;
        dwBuildNumber: DWORD;
        dwPlatformId: DWORD;
        szCSDVersion: array[0..127] of AnsiChar; { Maintenance string for PSS usage }
        wServicePackMajor: Word;
        wServicePackMinor: Word;
        wSuiteMask: Word;
        wProductType: Byte;
        wReserved: byte;
      end;
      TOSVersionInfoExW = record
        dwOSVersionInfoSize: DWORD;
        dwMajorVersion: DWORD;
        dwMinorVersion: DWORD;
        dwBuildNumber: DWORD;
        dwPlatformId: DWORD;
        szCSDVersion: array[0..127] of WideChar; { Maintenance string for PSS usage }
        wServicePackMajor: Word;
        wServicePackMinor: Word;
        wSuiteMask: Word;
        wProductType: Byte;
        wReserved: byte;
      end;
      TOSVersionInfoEx = TOSVersionInfoExA;

    TProdVersion = (Unknown, WinNT_40_Workstation, WinXP_Home, WinXP_MediaCenter,
       WinXP_Starter, WinXP_Tablet, Win2000_Professional, WinXP_Professional,
       WinVistaHome, WinServer2008_DataCenter, WinServer2008_Enterprise,
       WinServer2008, WinServer2003_IA64_DataCenter,
       WinServer2003_IA64_Enterprise, WinServer2003_IA64,
       WinServer2003_AMD64_DataCenter, WinServer2003_AMD64_Enterprise,
       WinServer2003_AMD64, WinServer2003_DataCenter, WinServer2003_Enterprise,
       WinServer2003_WebEdition,  WinServer2003, Win2000_Server_Datacenter,
       Win2000_Server_Advanced, Win2000_Server, WinNT_40_Server_Enterprise,
       WinNT_40_Server);

  const
    Prod_strings: array[Unknown..WinNT_40_Server] of string =
    ('Unknown Product Type', 'Windows NT 4.0 Workstation', 'Windows XP Home',
     'Windows XP Media Center', 'Windows XP Starter', 'Windows XP Tablet Edition',
     'Windows 2000 Professional', 'Windows XP Professional', 'Windows Vista Home',
     'Windows Server 2008 DataCenter', 'Windows Server 2008 Enterprise',
     'Windows Server 2008', 'Windows Server 2003 IA64 DataCenter',
     'Windows Server 2003 IA64 Enterprise', 'Windows Server 2003 IA64',
     'Windows Server 2003 AMD64 DataCenter', 'Windows Server 2003 AMD64 Enterprise',
     'Windows Server 2003 AMD64', 'Windows Server 2003 DataCenter',
     'Windows Server 2003 Enterprise', 'Windows Server 2003 Web Edition',
     'Windows Server 2003', 'Windows 2000 Server DataCenter',
     'Windows 2000 Server Advanced', 'Windows 2000 Server',
     'Windows NT 4.0 Server Enterprise', 'Windows NT 4.0 Server');

  function GetExVersionExA(var lpVersionInformation: TOSVersionInfoExA): boolean; stdcall;
  function GetExVersionExW(var lpVersionInformation: TOSVersionInfoExW): boolean; stdcall;
  procedure GetProdVersion(var inprod: TProdVersion);

  function is32bitOS: boolean;
  function is64bitOS: boolean;

  implementation

    var
      SysInfo: TSystemInfo;

  function is32bitOS: boolean;
    var
      SysInfo: TSystemInfo;
    begin
      GetSystemInfo(SysInfo);
      Result := (
           (Sysinfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL)
        or (Sysinfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_IA32_ON_WIN64)
        );
    end;

  function is64bitOS: boolean;
    var
      SysInfo: TSystemInfo;
    begin
      GetSystemInfo(SysInfo);
      Result := (
           (Sysinfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64)
        or (Sysinfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_IA64)
        );
    end;

  function GetExVersionExA(var lpVersionInformation: TOSVersionInfoExA): boolean; stdcall;
      external 'Kernel32.dll' name 'GetVersionExA';
  function GetExVersionExW(var lpVersionInformation: TOSVersionInfoExW): boolean; stdcall;
      external 'Kernel32.dll' name 'GetVersionExW';

  procedure ProdXPHandle(OsVerInfo: TOSVersionInfoExA; var inprod: TProdVersion);
    { handle special XP OS cases (tablet, mediacenter, and starter).
      Not sure where the call to this fits though }
    begin
      if (inprod = WinXP_Home) or (inprod = WinXP_Professional) then
        begin
          if GetSystemMetrics(SM_TABLETPC) <> 0 then
            inprod := WinXP_Tablet
          else
          if GetSystemMetrics(SM_MEDIACENTER) <> 0 then
            inprod := WinXP_MediaCenter
          else
          if GetSystemMetrics(SM_STARTER) <> 0 then
            inprod := WinXP_Starter;
        end;
    end;

  procedure ProdWSHandle(OsVerInfo: TOSVersionInfoExA; var inprod: TProdVersion);
    { handle the workstation product line for Windows NT and above }
    begin
      case OsVerInfo.dwMajorVersion of
        4: inprod := WinNT_40_Workstation;
        5: if (OsVerInfo.wSuiteMask and VER_SUITE_PERSONAL) > 0 then
              case OsVerInfo.dwMinorVersion of
                0: inprod := Win2000_Professional;
                1: inprod := WinXP_Home;
              end
           else
              case OsVerInfo.dwMinorVersion of
                0: inprod := Win2000_Professional;
                1: inprod := WinXP_Professional;
              end;
        6: if (OsVerinfo.wSuiteMask and VER_SUITE_PERSONAL) > 0 then
              inprod := WinVistaHome;
      end;
      // repeating here because I'm guessing these are workstation OSes, but not
      // sure otherwise what the suite mask returns for these.
      ProdXPHandle(OsVerInfo, inprod);
    end;

  procedure ProdSVHandlev5(OsVerInfo: TOSVersionInfoExA; var inprod: TProdVersion);
    { handle v5 series Windows servers }
    begin
      if OsVerInfo.dwMinorVersion = 2 then  // server 2003
        begin
          if SysInfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_IA64 then
            begin
              if (OsVerInfo.wSuiteMask and VER_SUITE_DATACENTER) > 0 then
                inprod := WinServer2003_IA64_Datacenter
              else
              if (OsVerInfo.wSuiteMask and VER_SUITE_ENTERPRISE) > 0 then
                inprod := WinServer2003_IA64_Enterprise
              else
                inprod := WinServer2003_IA64;
            end
          else
          if SysInfo.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64 then
            begin
              if (OsVerInfo.wSuiteMask and VER_SUITE_DATACENTER) > 0 then
                inprod := WinServer2003_AMD64_Datacenter
              else
              if (OsVerInfo.wSuiteMask and VER_SUITE_ENTERPRISE) > 0 then
                inprod := WinServer2003_AMD64_Enterprise
              else
                inprod := WinServer2003_AMD64;
            end
          else
            begin
              if (OsVerInfo.wSuiteMask and VER_SUITE_DATACENTER) > 0 then
                inprod := WinServer2003_Datacenter
              else
              if (OsVerInfo.wSuiteMask and VER_SUITE_ENTERPRISE) > 0 then
                inprod := WinServer2003_Enterprise
              else
              if (OsVerInfo.wSuiteMask and VER_SUITE_BLADE) > 0 then
                inprod := WinServer2003_WebEdition
              else
                inprod := WinServer2003;
            end;
        end;
      if OsVerInfo.dwMinorVersion = 0 then  // windows 2000
        begin
          if (OsVerInfo.wSuiteMask and VER_SUITE_DATACENTER) > 0 then
            inprod := Win2000_Server_Datacenter
          else
          if (OsVerInfo.wSuiteMask and VER_SUITE_ENTERPRISE) > 0 then
            inprod := Win2000_Server_Advanced
          else
            inprod := Win2000_Server;
        end;
     end;

  procedure ProdSVHandle(OsVerInfo: TOSVersionInfoExA; var inprod: TProdVersion);
    begin
      if (OsVerInfo.dwMajorVersion = 6) and (OsVerInfo.dwMinorVersion = 0) then
        begin
          if (OsVerInfo.wSuiteMask and VER_SUITE_DATACENTER) > 0 then
             inprod := WinServer2008_DataCenter
          else
          if (OsVerInfo.wSuiteMask and VER_SUITE_ENTERPRISE) > 0 then
             inprod := WinServer2008_Enterprise
          else
             inprod := WinServer2008;
        end
      else
      if (OsVerInfo.dwMajorVersion = 5) then
         ProdSVHandleV5(OsVerInfo, inprod)
      else
      if (OsVerInfo.wSuiteMask and VER_SUITE_ENTERPRISE) > 0 then
         inprod := WinNT_40_Server_Enterprise
      else
        inprod := WinNT_40_Server;
    end;

  procedure GetProdVersion(var inprod: TProdVersion);
    { handle logic for determining specific product version of WinNT systems }
    var
      OsVerInfo: TOSVersionInfoExA;
    begin
      inprod := Unknown;
      OSVerInfo.dwOsVersionInfoSize := Sizeof(TOsVersionInfoExA);
      if GetExVersionExA(OsVerInfo) then
         begin
           if OsVerInfo.dwPlatformId <> VER_PLATFORM_WIN32_NT then exit;
           if (OSVerInfo.wProductType = VER_NT_WORKSTATION) and
              (SysInfo.wProcessorArchitecture <> PROCESSOR_ARCHITECTURE_AMD64) then
              ProdWSHandle(OsVerInfo, inprod)
           else
             if OsVerInfo.wProductType in
               [VER_NT_SERVER, VER_NT_DOMAIN_CONTROLLER] then
               ProdSVHandle(OsVerInfo, inprod);
         end;
    end;

initialization
  GetSystemInfo(SysInfo);
finalization
end.

A small test program.
Code:
{$APPTYPE CONSOLE}
program prodmain; uses sysutils, prodver;
  { windows specific versioning demo }
  var
    Prodtype: TProdVersion;
  begin
    GetProdVersion(Prodtype);
    writeln(prod_strings[prodtype]);
    writeln('32 bit?: ', Is32BitOS);
    writeln('64 bit?: ', Is64BitOS);
    readln;
  end.

Here's what you need for the other unit to follow out on roo0047's idea:

Code:
const
      win_strings: array[wvUnknown..wv2008_r2] of string =
      ('Unknown Windows Type', 'Win32s on Windows 3.1', 'Windows 95',
       'Windows 95 OSR2', 'Windows 98', 'Windows 98 SE', 'Windows NT',
       'Windows ME', 'Windows 2000', 'Windows XP', 'Windows Home Server',
       'Windows XP Professional 64-bit', 'Windows Server 2003',
       'Windows Server 2003 R2', 'Windows Vista', 'Windows Server 2008',
       'Windows 7', 'Windows Server 2008 R2');

Hope this helps.



I'm waiting for the white paper entitled "Finding Employment in the Era of Occupational Irrelevancy
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top