Search code examples
batch-filecmdcommand-prompt

How to filter Home Directory location from net user accounts?


There is the command net user to list all User Accounts.
The output of the command is something like this:

Administrator            Guest                    asroot
1psaadm                  2satish                  3shyam                    
4sushil                  5sysuser_8               6sysuser_b                
tcuser                   test1                    test11 
...

If I run this command

net user shyam

the output is:

Workstations allowed         All
Logon script                 
User profile                 
Home directory               C:\Inetpub\vhosts\example.com
Last logon                   6/5/2021 4:57:17 AM

Logon hours allowed          All

I want only the Home Directory path from all user accounts. So I want only this path C:\Inetpub\vhosts\example.com from all user accounts.

I tried to get the wanted data with this command, but there is no output.

for /f "tokens=* skip=1" %a in ('wmic UserAccount get Name') do net user %a | findstr /c:"Home directory"

How can I get the list of all paths of all user accounts?

Note: If there is no Home directory path in your cmd then you can try to list:

Logon hours allowed      ALL

Solution

  • The output of WMIC is always a Unicode output with encoding UTF-16 LE (little endian) with BOM (byte order mark) which is a problem in this case because of the Windows command processor cmd.exe is designed for processing text data with a character encoding using just one byte per character. CMD interprets the UTF-16 LE encoded line ending with the hexadecimal byte values 0D 00 0A 00 (carriage return + line-feed) wrong as 0D 0D 0A and for that reason each line processed further from captured output has a carriage return at end after removing the line ending 0D 0A.

    There is one more problem with the command line:

    wmic UserAccount get Name
    

    WMIC appends on all lines trailing spaces so that every output line has the same number of characters. That is problematic as a user name can contain also one or more spaces and for that reason the default string delimiters normal space and horizontal tab cannot be used to remove the unwanted trailing spaces. The usage of tokens=* results in removing just all leading spaces/tabs, but the trailing spaces remain.

    What happens on using the following command line?

    for /f "tokens=* skip=1" %a in ('wmic UserAccount get Name') do
    

    There is assigned one user account name after the other to the specified loop variable a with trailing spaces and a carriage return at end and for that reason the next command fails to work as expected.

    In a command prompt window can be used:

    for /F "skip=2 tokens=1* delims==" %G in ('%SystemRoot%\System32\wbem\wmic.exe USERACCOUNT GET Name /VALUE 2^>nul') do @for /F "eol=| delims=" %I in ("%H") do @for /F "tokens=2*" %J in ('%SystemRoot%\System32\net.exe user "%I" 2^>nul ^| %SystemRoot%\System32\findstr.exe /B /L /C:"Home directory"') do @echo Home directory for "%I" is: "%K"
    

    In a batch file can be used:

    for /F "skip=2 tokens=1* delims==" %%G in ('%SystemRoot%\System32\wbem\wmic.exe USERACCOUNT GET Name /VALUE 2^>nul') do for /F "eol=| delims=" %%I in ("%%H") do for /F "tokens=2*" %%J in ('%SystemRoot%\System32\net.exe user "%%I" 2^>nul ^| %SystemRoot%\System32\findstr.exe /B /L /C:"Home directory"') do echo Home directory for "%%I" is: "%%K"
    

    This long command line is used best in a batch file as posted above, but better readable is:

    for /F "skip=2 tokens=1* delims==" %%G in ('%SystemRoot%\System32\wbem\wmic.exe USERACCOUNT GET Name /VALUE 2^>nul') do (
        for /F "eol=| delims=" %%I in ("%%H") do (
            for /F "tokens=2*" %%J in ('%SystemRoot%\System32\net.exe user "%%I" 2^>nul ^| %SystemRoot%\System32\findstr.exe /B /L /C:"Home directory"') do (
                echo Home directory for "%%I" is: "%%K"
            )
        )
    )
    

    The first command FOR with option /F and a string enclosed in ' starts in background one more command process with option /c to execute WMIC with full qualified file name and its four arguments with redirection 2>nul to redirect a possible error message from handle STDERR (standard error) to device NUL. So executed is in background with Windows being installed into C:\Windows the command:

    C:\Windows\System32\cmd.exe /c C:\Windows\System32\wbem\wmic.exe USERACCOUNT GET Name /VALUE 2>nul
    

    Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded wmic command line with using a separate command process started in background.

    WMIC uses in this case the Win32_UserAccount class as documented by Microsoft.

    The output of WMIC with option /VALUE at end is different to the output without this option. The output is now for each user account name:

    • two empty lines and
    • a line with Name= and user account name appended without trailing spaces which is one reason for using option /VALUE.

    There are output two more empty lines in this case at the end by WMIC.

    FOR respectively cmd.exe processing the batch file waits for self-termination of started cmd.exe and then processes the captured text data which was output to handle STDOUT (standard output) of the background command process.

    FOR with option /F ignores always empty lines. But the Unicode encoded output of WMIC is not correct processed unfortunately by cmd.exe and for that reason there are lines containing just a carriage return and lines starting with Name= and a user account name and an erroneous carriage return at the end. So it is not possible to process the captured output directly.

    The usage of the options skip=2 tokens=1* delims== results in

    • skipping the first two captured lines which are always empty lines,
    • splitting each line up into two substrings with the equal sign as string delimiter existing always once after Name on the lines of interest with the account name,
    • using the default end of line character ; which is no problem here as the lines of interest start always with Name
    • and getting assigned to specified loop variable G the carriage return on the wrong processed empty lines or Name on a line with a user account name and to next but one loop variable H according to the ASCII table either no string at all on the empty lines or the user account name with an unwanted carriage return at the end.

    NOTE: A user account name beginning very unusual with one or more equal signs would not be correct processed by this code as all equal signs at beginning of the user account name would be removed by FOR too.

    The second FOR again with option /F processes now just the string assigned to the loop variable H. So there is no string to process at all assigned to H for empty lines interpreted wrong as a line with a single carriage return and so the second FOR loop filters out the empty lines.

    For the lines with the user account name the name with the unwanted carriage return at end assigned to loop variable H is processed by the the second FOR with removing the newline character carriage return and assigned the rest to the specified loop variable I which is the wanted user account name. The option eol=| is used here to avoid that an unusual name starting with one or more semicolons is ignored by second FOR. No user account name can contain a vertical bar as this is a character not allowed for file/folder names.

    The third FOR loop once again with option /F runs in background again one more command process to execute NET with output redirected first to FINDSTR to filter out all lines except the line starting case-sensitive with Home directory. NET outputs the text data with a one byte per character encoding making it possible to filter the output with FINDSTR and processing the captured output by FOR more easily. The command executed in background is:

    C:\Windows\System32\cmd.exe /c C:\Windows\System32\net.exe user "%%I" 2>nul | C:\Windows\System32\findstr.exe /B /L /C:"Home directory"
    

    The third FOR has to process always only one line starting with Home directory, fifteen spaces for alignment and perhaps nothing more or the home directory path as defined for the user account. There is used the option tokens=2* to split up the line using default space/tab as line delimiters to get assigned the word directory to the specified loop variable J and the entire home directory path to next but one loop variable K which can contain also one or more spaces or being an empty string as on my Windows computer for all accounts.

    The third FOR executes finally the command ECHO to output the message with the user account name as determined by the second FOR and the home directory path as determined by third FOR if there is a home directory path at all.


    Compo suggested to use WMIC with the Win32_NetworkLoginProfile class by using:

    %SystemRoot%\System32\wbem\WMIC.exe NetLogin Get Caption,HomeDirectory
    

    That class has account name and home directory path as members and so the wanted data can be get directly with WMIC.

    The list of names is different between the two classes as it can be seen on running both WMIC commands in a command prompt window and reading the description for property Name of both classes carefully whereby the string of property Caption of Win32_NetworkLoginProfile class differs also from string of property Name of this class. The string of Caption is just the account name for a real user account while the string of Name is ComputerName\AccountName for a local account or Domain\AccountName for a domain account.

    However, it is perhaps possible to use the data of this class with the following code in a batch file:

    @echo off
    setlocal EnableExtensions DisableDelayedExpansion
    for /F "skip=2 tokens=1* delims==" %%G in ('%SystemRoot%\System32\wbem\wmic.exe NETLOGIN GET Caption^,HomeDirectory /VALUE 2^>nul') do (
        if "%%G" == "Caption" (
            set "AccountName="
            for /F "eol=| tokens=1,2 delims=\" %%I in ("%%H") do if "%%J" == "" set "AccountName=%%I"
        ) else if "%%G" == "HomeDirectory" (
            set "HomeDirectory="
            for /F "eol=| delims=" %%I in ("%%H") do set "HomeDirectory=%%"
            if defined AccountName (
                setlocal EnableDelayedExpansion
                echo Home directory for "!AccountName!" is: "!HomeDirectory!"
                endlocal
            )
        )
    )
    endlocal
    

    There is again the option /VALUE as otherwise there would be again trailing spaces after home directory path if there is a home directory path output at all for an account which is not the case on my Windows computer.

    The code is written to ignore the accounts containing in Caption a backslash like NT AUTHORITY\SYSTEM, NT AUTHORITY\LOCAL SERVICE and NT AUTHORITY\NETWORK SERVICE which are most likely not of interest at all. Well, it would be also possible to use if not "%%I" == "NT AUTHORITY" instead of if "%%J" == "" to filter out the accounts of no interest as not being real user accounts.

    The advantage of this solution is that there is started cmd.exe only once in background to run wmic.exe and get all the data of interest. So this solution is faster.


    To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.

    • cmd /?
    • echo /?
    • endlocal /?
    • for /?
    • if /?
    • net /?
    • net user /?
    • set /?
    • setlocal /?
    • wmic /?
    • wmic netlogin /?
    • wmic netlogin get /?
    • wmic useraccount /?
    • wmic useraccount get /?