
// WMITest.java
// Andrew Davison, ad@coe.psu.ac.th, Nov. 2022

/* Various uses of Powershell, concentrating on getting
   information by using the get-wmiobject cmdlet and
   its WMI classes.

   Info. on Powershell:
    * https://learn.microsoft.com/en-us/powershell/
    * https://ss64.com/ps
    * https://www.spguides.com/powershell-examples/
    * https://powershellbyexample.dev/

   Info on get-wmiobject:
     * https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-wmiobject?view=powershell-5.1

     * https://ss64.com/ps/get-wmiobject.html

     * https://adamtheautomator.com/get-wmiobject/

   Some common WMI classes:

       Win32_computerSystem
       Win32_bios
       Win32_baseboard    (Motherboard)
       Win32_processor    (32+64 bit processor info)
       Win32_Share        (File shares)
       Win32_LogicalDisk  (hard disk)
       Win32_PhysicalMemory
       Win32_Printer
       Win32_operatingSystem  (Virtual Memory)
       Win32_networkadapter

   Note: starting in PowerShell 3.0, get-wmiobject has been 
   superseded by get-ciminstance. This has become definite
   in Powershell 7 which doesn't support WMI at all. 
   You have to use CIM instead, and its get-ciminstance cmdlet. 

   However, I'm still using Powershell 5.1
   and Powershell 2! on my test machines, so I'm sticking
   with get-wmiobject

   ----
   This file includes a powershell utilty function runPS()
   which hides some of the boiler-plate required for
   Powershell. There's also a wmiQuery() function which hides
   some of the setup needed to use get-wmiobject

    Usage:
      > compile WMITest.java
      > run WMITest
*/


import java.io.*;
import java.util.*;

import org.zeroturnaround.exec.*;



public class WMITest
{

  public static void main(String[] args)
  {
    if (ZUtils.cmdExit("powershell", "-command", 
                              "$PSVersionTable") != 0) {
      System.out.println("powershell not found");
      return;
    }
    System.out.println("Using Powershell version: " +
                           runPS1("get-host | select Version"));

    // uncomment one or more of these functions...
    System.out.println("\n--------------- WMI --------------------");
    useWMI();
    System.out.println("\n--------------- Software --------------------");
    installedSoftware();
    System.out.println("\n---------------- Processes -----------------");
    examineProcs();
  }  // end of main()



  private static void useWMI()
  /* Print various pieces of system information by querying
     the system with the get-wmiobject cmdlet, a
     WMI class , and a class property.
  */
  {
    System.out.println("System properties: ");
    String[] cmd = {"powershell", "-command", 
               "\"get-wmiobject\" win32_Computersystem" }; 
    System.out.println( ZUtils.cmdOut(cmd).trim());


    /* wmiQuery() and wmiQuery1() always use get-wmiobject,
       so the user only needs to supply the 
       WMI class and property name
    */
    System.out.println("\nVarious System Info:");

    String result = wmiQuery1("win32_Computersystem", "Name");
                                // WMI class, property
    System.out.println("  System Name: " + result);

    System.out.println("  " + wmiQuery1("win32_bios", "Name"));

    result = wmiQuery1("  win32_baseboard", "Manufacturer");
    System.out.println("  Manufacturer: " + result);

    System.out.println("  " + wmiQuery1("win32_processor", "Caption"));

    result = wmiQuery1("  win32_processor", "NumberOfCores");
    System.out.println("  No. of cores: " + result);


    // list all the property names used by the win32_bios class
    String psQuery = "get-wmiobject win32_bios | " +
                     "get-Member -membertype property | " +
                     "select Name";
    List<String> res = runPS(psQuery);
    System.out.println("\nProperty names for the win32_bios class:\n" + res);

    if (ZUtils.cmdExit("powershell", "-command", "get-netadapter") != 0)
      System.out.println("get-netadapter not found");

    res = wmiQuery("Win32_networkadapter", "Name");
    System.out.println("\nNetwork adapters:");
    for(String line : res)
      System.out.println("  " + line);
    System.out.println();

  } // end of useWMI()



  private static void installedSoftware()
  /* This function shows two ways to print a list of 
     installed software -- by using the win32_product
     WMI class, or by querying the Windows registry 
     using the Get-ItemProperty cmdlet.

     The second approach is both faster and returns a
     more complete list.
  */
  {
    System.out.println("Installed Software:\n");
    // res = wmiQuery("win32_product", "Name");   // too slow

    /* query the Windows registry;
       the HKLM key contains lots of settings related to 
       the local machine
    */
    String psQuery = "Get-ItemProperty  " + 
                  "HKLM:\\Software\\Wow6432Node\\Microsoft\\Windows\\" +
                  "CurrentVersion\\Uninstall\\*" +
           " | select DisplayName";
    List<String> res = runPS(psQuery);

    // the output here can be very, very long...
    Collections.sort(res, String.CASE_INSENSITIVE_ORDER);
    for(int i=0; i < res.size(); i++)
      System.out.println((i+1) + ".  " + res.get(i));   
  }  // end of installedSoftware()



  private static void examineProcs()
  /* The code here correctly returns process information, but
     the second example employs the Get-Process cmdlet to return
     several columns of data. However, my runPS() support function
     assumes that the Powerhshell command generates a single
     column of data, and so incorrectly formats the
     multiple columns as a single column.

     In particular, it leaves ","s inside the string to indicate 
     the separate columns.
     That's why there's a for-loop to replace that text with spaces.
  */
  {
    // the output here can be very, very long
/*
    System.out.println("Process Info:");
    String[] cmd = {"powershell", "-command", "\"get-process\"" }; 
    System.out.println( ZUtils.cmdOut(cmd));
*/
    System.out.println("Top five most CPU intensive processes:\n");
    String psQuery = "Get-Process | sort -descending CPU | select -first 5 | select ProcessName, CPU";
    List<String> res = runPS(psQuery);

    for(String line : res)
      // System.out.println("  " + line);  // quotes left inside line!
      System.out.println("  " + line.replace("\",\"", "\t"));  
                                         // quotes removed
    System.out.println();
  }  // end of examineProcs()



  // -------------- Powershell utilities ---------------------


  private static List<String> wmiQuery(String classNm, String propNm)
  /* Build a powershell call to the get-wmiobject cmdlet using
     the specified class name and property. 
  */
  { String psQuery = "get-wmiobject " + classNm + 
                                 " | select " + propNm;
    return runPS(psQuery);
  }  // end of wmiQuery()


  private static String wmiQuery1(String classNm, String propNm)
  // a version of wmiQuery() that only returns the first result
  { 
    String psQuery = "get-wmiobject " + classNm + 
                                 " | select " + propNm;
    return runPS1(psQuery);
  }  


  private static List<String> runPS(String psQuery)
  /* Extend a Powershell query to generate CSV output, and
     convert that to an array of strings with quotes around
     the data.
     I assume that the query generates a single column of data.
     I process the answer in CSV format since it deals with null values.
  */
  {
    String[] cmd = {"powershell", "-command",
      "\"" + psQuery + 
           "\" | convertto-csv -NoTypeInformation" +  
                  // I cannot use '-UseQuotes Never' 
                  // since it's only in Powershell v.7
           " | select -skip 1 \"" };    // remove CSV header
    String resStr = ZUtils.cmdOut(cmd);

    // build list by dividing on newlines
    String[] resArr = resStr.split("\\R");

    for (int i=0; i < resArr.length; i++)
       resArr[i] = resArr[i].replaceAll("^\"|\"$", "");   
                    // get rid of CSV quotes around the data
    return Arrays.asList(resArr);
  }  // end of runPS()



  private static String runPS1(String psQuery)
  // a version of runPS() that only returns the first result
  {  
    List<String> res = runPS(psQuery);
    if ((res == null) || (res.size() == 0)) {
      System.out.println("No result found");
      return null;
    }
    else
      return res.get(0);
  }  // end of runPS1()


}  // end of WMITest class