Search code examples
powershellbatch-filewmic

What's the cmd / batch alternative to the deprecated wmic?


I'm aware of wmic being deprecated as of 2012 and I now typically use PowerShell anyway, so had moved away from it already.

However, I frequently encounter cases where I need to work within batch files and do not have the option of migrating the solution to PowerShell.

My question is this: with wmic deprecated, what is the recommended alternative for continued access to the Windows Management Instrumentation from the command prompt and from batch files?

The best I can think of currently is something along the lines of:

powershell -command "(Get-CimInstance Win32_Processor) | select *" | some_other_program.exe

(this example to get processor attributes)

But one major downside of that is that errorlevels and the environment are no longer accessible. That is, the powershell command completes successfully, even if the command does not). And changes made to the environment in the powershell command do not propagate to the batch file.

Any suggestions on how to specifically address those issues, or on better alternatives to calling powershell from the batch for access to WMI are welcome, but specifically I'd like to know how to run a powershell command and access the results other than the standard out.


Solution

  • But one major downside of that is that errorlevels and the environment are no longer accessible.

    The simplest way to make a PowerShell command invoked via the PowerShell CLI (powershell.exe in Windows PowerShell, pwsh in PowerShell (Core) 7+) report a nonzero exit code is to throw a script-terminating (thread-terminating) error, which results in error code 1 - which your batch file can test for:

    @echo off
    
    :: # Simulate a failing CIM call
    powershell -c "$ErrorActionPreference='Stop'; Get-CimInstance Win32_NoSuchClass" 
    
    :: # Exit, if the PowerShell command failed.
    if ERRORLEVEL 1 echo Error: Get-CimInstance failed>&2 & exit /b %ERRORLEVEL%
    
    echo Moving on...
    

    $ErrorActionPreference='Stop' at the start of the command ensures that any subsequent error becomes a script-terminating one.
    To ignore non-terminating errors selectively, use -ErrorAction Ignore command-individually.
    If no error occurs, the exit code is implicitly 0.

    PowerShell's error handling is bewilderingly complex; see this GitHub issue for a comprehensive summary.

    Note:

    • Native PowerShell commands such as Get-CimInstance do not set exit codes; the best you can do is to map errors to exit code 1, and success to exit code 0.

    • If you call external programs from PowerShell, their exit code is reflected in automatic variable $LASTEXITCODE; you can use exit $LASTEXITCODE to pass that exit code through from your PowerShell command string; however, you can generally simply call external programs from your batch file directly - no detour via PowerShell needed.


    And changes made to the environment in the powershell command do not propagate to the batch file.

    True, the PowerShell command invariably runs in a child process, whose environment has no impact on the calling process.

    Generally:

    • Capture output from the PowerShell command in a file or process it in memory with a for /f loop (see example below) - unless passing the output through is sufficient.

    • If you truly need to modify the calling batch file's environment, have the PowerShell command output the data for those modifications, and apply them in the batch file.

      • E.g., if you want your PowerShell command to set environment variables, make it output <name>=<value> string pairs that the batch file can use to set these variables.

    An example of tailoring output from a PowerShell command for parsing in a batch file with for /f; note that the quoting can get tricky:

    @echo off
    
    :: # Get the .Name and .MaxClockSpeed property values from the Win32_Processor instance.
    for /f "tokens=1,* delims==" %%i in ('powershell -c "$ErrorActionPreference='Stop'; Get-CimInstance Win32_Processor | %% { foreach ($n in 'Name', 'MaxClockSpeed') { \""$n=$($_.$n)\"" } }"') do (
      echo [%%i]=[%%j]
    )
    
    :: # Exit, if the PowerShell command failed.
    if ERRORLEVEL 1 echo Error: Get-CimInstance failed>&2 & exit /b %ERRORLEVEL%
    

    The above yields something like:

    [Name]=[Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz]
    [MaxClockSpeed]=[2304]