5

Good day all,

So I wrote a simple .bat file I was going to call on user startup to get the MS Office versions. Tested fine for admins but unfortuatnely not so with standard users. I suspect this is because we have: Prevent access to registry editing tools setup in the GPO, though disable regedit from running silenty is set to 'no'.

So my questions is a) is that the likely reason why my script isn't working and b) if so is there a regedit alternative. Trying to keep it nice and simple.

Code below:

@echo off

reg query HKEY_CLASSES_ROOT\Access.Application\CurVer
if errorlevel 1 goto five

for /f "tokens=3" %%i in ('reg query HKEY_CLASSES_ROOT\Access.Application\CurVer') do (
  if %%i equ Access.Application.15 goto one
  if %%i equ Access.Application.14 goto two
  if %%i equ Access.Application.12 goto three
  if %%i equ Access.Application.11 goto four
  goto five
)

:one REM 2013 Pro
echo %computername%,Office 2013 Pro" >>\\SERVERNAME\bslogs$\officeVersions.csv
exit
:two REM 2010 Pro
echo %computername%,Office 2010 Pro >>\\SERVERNAME\bslogs$\officeVersions.csv
exit
:three REM 2007 Pro
echo %computername%,Office 2007 Pro" >>\\SERVERNAME\bslogs$\officeVersions.csv
exit
:four REM 2003 Pro
echo %computername%,Office 2003 Pro" >>\\SERVERNAME\bslogs$\officeVersions.csv
exit

:five REM Not Pro Verion
reg query HKEY_CLASSES_ROOT\Word.Application\CurVer
if errorlevel 1 goto ten

for /f "tokens=3" %%i in ('reg query HKEY_CLASSES_ROOT\Word.Application\CurVer') do (
  if %%i equ Word.Application.15 goto six
  if %%i equ Word.Application.14 goto seven
  if %%i equ Word.Application.12 goto eight
  if %%i equ Word.Application.11 goto nine
  goto ten
)

:six REM 2013 STD
echo %computername%,Office 2013 Std" >>\\SERVERNAME\bslogs$\officeVersions.csv
exit
:seven REM 2010 STD
echo %computername%,Office 2010 Std" >>\\SERVERNAME\bslogs$\officeVersions.csv
exit
:eight REM 2007 STD
echo %computername%,Office 2007 Std" >>\\SERVERNAME\bslogs$\officeVersions.csv
exit
:nine REM 2003 STD
echo %computername%,Office 2003 Std" >>\\SERVERNAME\bslogs$\officeVersions.csv
exit

:ten REM no idea
echo %computername%,????" >>\\SERVERNAME\bslogs$\officeVersions.csv exit

Many thanks, Martin

MartinR
  • 53
  • 5
  • There's a wmi class that can read the registry. It's not nice and simple though. I'll post an answer whenever I get a chance, unless someone else feels like demonstrating. – rojo Feb 09 '16 at 17:45

1 Answers1

5

a) is that the likely reason why my script isn't working

I dunno. Sounds plausible.

b) if so is there a regedit alternative

You can query registry values with WMI and the StdRegProv class.

Trying to keep it nice and simple.

Sorry. It is neither nice nor simple.

I've had to simulate reg.exe using WMI queries in the past to query remote registries. I hacked together a :getRegValue function that automates much of this. Run this and see whether it will work for what you have in mind.

@echo off
setlocal

call :getRegValue accessVer HKCR\Access.Application\CurVer\
echo Result: %accessVer%

call :getRegValue shell "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell"
echo Result: %shell%

goto :EOF

:getRegValue <return_var> <root\path\valname>
setlocal enabledelayedexpansion
rem // constants
set "hive[HKCR]=&H80000000"
set "hive[HKCU]=&H80000001"
set "hive[HKLM]=&H80000002"
set "hive[HKU]=&H80000003"
set "hive[HKCC]=&H80000005"

set "type[1]=GetStringValue"
set "type[2]=GetExpandedStringValue"
set "type[3]=GetBinaryValue"
set "type[4]=GetDWORDValue"
set "type[7]=GetMultiStringValue"
set "type[11]=GetQWORDValue"

SET "wmic=wmic /namespace:\\root\default class stdregprov call"

rem // split %~2 into hive, path, leaf
set "regpath=%~2"
if "%regpath:~-1%"=="\" set "regpath=%regpath%(Default)"
set hive=%regpath:\=&rem;%
set "regpath=!regpath:%hive%\=!"
set "leaf=%regpath%"
:leaf
set "leaf=%leaf:*\=%"
if not "%leaf%"=="%leaf:\=%" goto leaf
set "regpath=!regpath:\%leaf%=!"

set "hive=!hive[%hive%]!"

rem // get data type of leaf (default: string)
set "method=%type[1]%"
for %%I in (names types nameidx) do set "%%I="

2>NUL (
    for /f "tokens=2 delims={}" %%I in (
        '%wmic% EnumValues hDefKey^="%hive%" sSubkeyName^="%regpath:\=\\%"^
        ^| find "= {"'
    ) do (
        if not defined names (set "names=%%I") else set "types=%%I"
    )
)

if defined names (
    for %%n in (names types) do (
        set idx=0
        for %%I in (!%%n!) do (
            if defined nameidx (
                if !idx! equ !nameidx! set "method=!type[%%~I]!"
            ) else if /i "%%~I"=="%leaf%" (
                set "nameidx=!idx!"
            )
            set /a idx += 1
        )
    )
)

if /i "%leaf%"=="(Default)" set "leaf="

rem // get data value of leaf
2>NUL (
    for /f "delims=" %%I in (
        '%wmic% %method% hDefKey^="%hive%" sSubkeyName^="%regpath:\=\\%"^
        sValueName^="%leaf%" ^| findstr "[su]Value"'
    ) do (
        for %%# in (%%I) do set "ret=%%~#"
    )
)
endlocal & set "%~1=%ret%" & goto :EOF

As additional food for thought, wmic.exe supports remote queries with the following syntax:

wmic /node:remotePC /user:domain\remoteAdmin /password:password verbs...

Rather than having each user run your script at logon, you could use the remote switches to query all machines in batch.


Partly as an academic exercise and partly to head off anyone who might be thinking "You should use PowerShell", I rewrote the :getRegValue function to invoke PowerShell hybrid code. Unfortunately, querying the registry via WMI with PowerShell is still complicated. Although the string operations and object retrieval are easier, the marginally more economical code doesn't quite justify the added execution time.

<# : regval.bat
@echo off
setlocal

call :getRegValue accessVer HKCR\Access.Application\CurVer\
echo Result: %accessVer%

call :getRegValue shell "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell"
echo Result: %shell%

goto :EOF

:getRegValue <return_var> <root\path\valname>
setlocal
set "regpath=%~2"

for /f "delims=" %%I in (
    'powershell -noprofile -noninteractive "iex (gc \"%~f0\" | out-string)"'
) do (
    endlocal
    set "%~1=%%~I"
)
goto :EOF
: powershell hybrid code #>

$hiveConst = @{
    "HKCR" = 2147483648
    "HKCU" = 2147483649
    "HKLM" = 2147483650
    "HKU" = 2147483651
    "HKCC" = 2147483653
}

# http://www.vistax64.com/powershell/10160-powershell-remote-registry-access-via-wmi.html
$reg = gwmi -List -Namespace root\default | ?{ $_.Name -eq "StdRegProv" }

# split $env:regpath into $hive, $regpath, $leaf
$regpath = $env:regpath -split "\\"
$hive = $hiveConst[$regpath[0]]
$leaf = $regpath[$regpath.length - 1]
if ($leaf) {
    $regpath = $regpath[1..($regpath.length - 2)] -join "\"
} else {
    $regpath = $regpath[1..($regpath.length - 1)] -join "\"
}

if ($leaf -match "^\(Default\)$") { $leaf = "" }

# get data type of leaf (default: string)
$method = 1
$res = $reg.EnumValues($hive, $regpath)
for ($i = 0; $i -lt $res.sNames.length; $i++) {
    if ($res.sNames[$i] -eq $leaf) {
        $method = $res.Types[$i]
    }
}

# get data value of leaf
switch ($method) {
    1 { $reg.GetStringValue($hive, $regpath, $leaf).sValue; break }
    2 { $reg.GetExpandedStringValue($hive, $regpath, $leaf).sValue; break }
    3 { $reg.GetBinaryValue($hive, $regpath, $leaf).uValue; break }
    4 { $reg.GetDWORDValue($hive, $regpath, $leaf).uValue; break }
    7 { $reg.GetMultiStringValue($hive, $regpath, $leaf).sValue; break }
    11 { $reg.GetQWORDValue($hive, $regpath, $leaf).uValue; break }
}

Just because I felt like a challenge, I decided to try a third version employing JScript hybrid code this time. Believe it or not, this is the fastest of all three methods.

@if (@CodeSection == @Batch) @then
@echo off & setlocal

call :getRegValue accessVer HKCR\Access.Application\CurVer\
echo Result: %accessVer%

call :getRegValue shell "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell"
echo Result: %shell%

goto :EOF

:getRegValue <return_var> <root\path\valname>
for /f "delims=" %%I in ('cscript /nologo /e:JScript "%~f0" "%~2"') do set "%~1=%%~I"
goto :EOF

@end // end batch / begin JScript hybrid code

function reg(strMethod, objParams) {
    try {
        var winmgmts = GetObject('winmgmts:root\\default'),
            StdRegProv = winmgmts.Get('StdRegProv');
            params = StdRegProv.Methods_(strMethod).InParameters.SpawnInstance_();

        for (var i in objParams) params[i] = objParams[i];
        return winmgmts.ExecMethod('StdRegProv', strMethod, params);
    }
    catch(e) { return {'sValue':''} }
};

var hiveConst = {
        "HKCR" : 2147483648,
        "HKCU" : 2147483649,
        "HKLM" : 2147483650,
        "HKU" : 2147483651,
        "HKCC" : 2147483653
    },
    methodConst = {
        "1" : "GetStringValue",
        "2" : "GetExpandedStringValue",
        "3" : "GetBinaryValue",
        "4" : "GetDWORDValue",
        "7" : "GetMultiStringValue",
        "11" : "GetQWORDValue"
    },
    regpath = WSH.Arguments(0).split('\\'),
    hive = hiveConst[regpath.shift()],
    leaf = regpath.pop(),
    regpath = regpath.join('\\');

if (/^\(Default\)$/.test(leaf)) leaf = '';

// get data type of leaf (default: string)
try {
    var params = {'hDefKey': hive, 'sSubKeyName': regpath},
        res = reg('EnumValues', params),
        sNames = res.sNames.toArray(),
        Types = res.Types.toArray();

    for (var i in sNames) {
        if (sNames[i] == leaf) var method = methodConst[Types[i]];
    }
}
catch(e) { var method = methodConst[1] }

// get and output data value of leaf
var params = {'hDefKey': hive, 'sSubKeyName': regpath, 'sValueName': leaf},
    res = reg(method, params);
WSH.Echo(res.sValue || res.uValue);
rojo
  • 24,000
  • 5
  • 55
  • 101
  • 1
    That is a truly amazing function, thank you. Unfortunately some of the script is beyond me, I'm looking at the error handling and I cannot see why I get an output on the function of '2' when the regkey does exist? This is needed as checking for Pro versions of office based on whether access is installed, then moving onto Word versions. – MartinR Feb 10 '16 at 10:43
  • regarding calling remotely, I did try that but unfortunately just get blank results. I thought it might be because was using HKCR so changed to 'HKLM\Software\Classes\Word.Application\CurVer\' however do joy. `SET "wmic=wmic /node:"COMPUTERNAME" /user:DOMAIN\USER /password:PASSWORD /namespace:\\root\default class stdregprov call"` – MartinR Feb 10 '16 at 11:16
  • An update to this. I really do not know why WMIC wasn't working with the script remotely as have used it remotely to call keys here before. As a workaround using PSEXEC with list functionality to do the same thing. Will prob add to login script for a few days to catch machines turned off and consolidate data in Excel. Would have preferred to keep it in the script but will suffice. In my original script I'm just outputting ???? when your `:getRegValue` function doesn't find expected versions. This will have to do until I understand it all more. Many many thanks for your help. – MartinR Feb 10 '16 at 12:55
  • 1
    @MartinR I fixed a problem with the pure batch version where `find "Value"` was inappropriately matching `ReturnValue`. Now the function should return null when the key doesn't exist, and you can use `if not defined accessVar` to determine whether Office Pro is installed. – rojo Feb 10 '16 at 14:10
  • 1
    @MartinR I just tested adding `/node:remotepc` and querying `HKCR` remotely worked with no problems here. Seems to work both with and without quotation marks around the PC name. Weird. One thing to be careful of is that you should leave the trailing backslash at the end of "CurVer\" (or use `CurVer\(Default)` if you wish). I'm guessing you already did that, though. – rojo Feb 10 '16 at 21:07
  • thanks. That's great, always fill happier when I understand the error handling. It's working well, still not with the /node but I can live with that for now using psexec or GPO's. Will have a more indepth look when I get a chance. I'm certain I'll be using your function for many other uses. Cheers – MartinR Feb 12 '16 at 12:33