Rob van der Woude's Scripting Pages

User Input

Sometimes we need some user interaction in our batch files.
We may need to know to which directory a file is to be copied, for example.
Or which drive needs to be formatted.

There are many ways to achieve this user interaction.

The most basic form of user interaction, of course, is the PAUSE command, which halts the batch file until the user presses "any key" (apart from Ctrl, Alt, Shift, CapsLock, NumLock or ScrollLock).
Maybe not really sophisticated, but it works and is always available, in all DOS, Windows and OS/2 versions.

How you can manage to retrieve user input depends on the OS version used, and whether you want to allow "third party" tools or use "native code" only.

Windows 2000 and later versions

As of Windows 2000, user input can be obtained quite easily by using SET /P

SET /P variable=[promptString]

This command will display an optional promptString where the user can type in a string and press Enter. The typed string will then be stored in the specified environment variable variable.

Warning: Working with a stored string containing ampersands and doublequotes may cause your batch file to fail or even to execute "unexpected" code (code insertion).
Do not use SET /P in batch files running with elevated privileges!

Windows NT 4 and later versions

In NT we don't need temporary files and we can skip a few lines by using TYPE CON and FOR /F:

  1. @ECHO OFF
  2. :: UserInNT.bat
  3. :: How to use the TYPE CON command to receive user input
  4. :: Written by Rob van der Woude
  5. :: http://www.robvanderwoude.com
  6.  
  7. ECHO.
  8. ECHO Demonstration of receiving user input through the TYPE CON command.
  9. ECHO Type in any string and close by pressing Enter, F6 (or Ctrl+Z), Enter.
  10. ECHO Only the last non-empty line will be remembered, leading spaces are ignored.
  11. ECHO.
  12.  
  13. :: Only one single command line is needed to receive user input
  14. FOR /F "tokens=*" %%A IN ('TYPE CON') DO SET INPUT=%%A
  15. :: Use quotes if you want to display redirection characters as well
  16. ECHO You typed: "%INPUT%"
  17.  

It is still just as un-intuitive as the first COPY CON example, though.

 
Click to download the ZIPped sources
 
Update: Replace TYPE CON by MORE in the above NT batch file and you can save yourself pressing Enter once — you'll need to press F6, Enter only instead of Enter, F6, Enter, though the latter will still work.
[Tip posted by "Frank" on alt.msdos.batch.nt]

Tom Lavedas' New and Improved Data Input Routine! shows a way to use a graphical box to ask for user input in Windows 95

Duke Communications International, Inc.'s tip #0323: How can I get a batch file to prompt me for parameters? shows a way to get a graphical box asking for user input in Windows NT

My own DialogBoxes (executables, written in C#) can be used to display messages and ask for input in Windows XP and later versions.

Walter Zackery posted two interesting solutions to obtain user input in NT, on the alt.msdos.batch.nt news group.

One solution uses the FORMAT command, which will fail since it tries to format a diskette in drive A: at 160KB.
If you have a 5¼" drive as your A: drive don't use this one.

The second solution uses the LABEL command.
It will actually change drive C:'s volume label and then restore it again to its old value.
The volume label in NT is restricted to 32 characters, and so is the input string when using this LABEL trick for user input.
Besides that the batch file limits the string to 2 words (1 space) only.

I adapted the original postings so that the batch files no longer need to use temporary files. You can view the original postings at my Solutions found at alt.msdos.batch page.

Using FORMAT:

  1. @ECHO OFF
  2. :: Obtaining user input in Windows NT
  3. :: with the use of the FORMAT command
  4. :: posted August 20,1999
  5. :: Author: Walter Zackery
  6. :: Adapted by Rob van der Woude
  7. :: (no longer uses temporary file)
  8. :: http://www.robvanderwoude.com
  9.  
  10. SET INPUT=
  11. CLS
  12. ECHO\
  13. ECHO Enter your input string and press ENTER when done.
  14. ECHO\
  15. FOR /F "tokens=5* delims= " %%A IN ('FORMAT /F:160 A: ^| FIND "..."') DO SET INPUT=%%B
  16. SET INPUT
  17.  

Using LABEL:

  1. @ECHO OFF
  2. :: Obtaining user input in Windows NT
  3. :: with the use of the LABEL command
  4. :: posted August 20,1999
  5. :: Author: Walter Zackery
  6. ::
  7. :: Adapted by Rob van der Woude
  8. :: (no longer uses a temporary file;
  9. :: redirection of label command's
  10. :: standard error prevents label command's
  11. :: own prompt to be displayed on screen)
  12.  
  13. CLS
  14. ECHO.
  15. FOR /F "tokens=5* delims= " %%A IN ('VOL C: ^| FIND "drive C"') DO SET OLDLABEL=%%B
  16. ECHO Enter some input string, containing no more than 32 characters:
  17. FOR /F "TOKENS=*" %%? IN ('LABEL C: 2^>NUL') DO SET INPUT=%%?
  18. SET INPUT
  19. LABEL C: %OLDLABEL%
  20.  
 
Click to download the ZIPped sources  
 

Clay Calvert posted another interesting solution to obtain Yes or No input in most DOS versions by using DEL's /P switch (prompt for confirmation).

Another great solution by Eric Phelps uses a temporary HTA file to obscure a password while it is being typed.

MS-DOS

In the MS-DOS 3 days, a simple Yes/No question could be answered by changing to a temporary directory where two temporary batch files were located, Y.BAT and N.BAT.
Guess what happened if a user typed a completely different answer . . .

Since MS-DOS 6 we have CHOICE.COM (CHOICE.EXE in later versions), a much more versatile and reliable way to solve one character answers like Yes/No.

The CHOICE command was discontinued in Windows NT 4, only to be reintroduced again in Windows 7 (and maybe Vista?).
If you work with one of these "challenged" Windows versions, you may want to try my Poor Man's Choice instead, or use DEBUG to create REPLY.COM (16-bit, won't work in a 64-bit OS), as published in Microsoft Knowledge Base article Q77457: Accepting Keyboard Input in Batch Files.

There is another way to receive user input: COPY CON

The command:

COPY CON filename

copies the user input on the command line to the file filename.
To stop entering user input, the user has to type Ctrl+Z (or F6), followed by the Enter key.

Many PC users and batch file authors (including myself), find this approach "less intuitive", to say the least. One would expect that pressing the enter key is enough, and once you find out it isn't, the previous line of input cannot be removed anymore.

The following trick uses ANSI to perform some key translation: the Enter key is translated to the F6 key followed by the Enter key. Thus only one line of input can be entered, and pressing the Enter key sends the input to the temporary file USERINP.TMP.

  1. ECHO Enter some input, and press Enter when ready . . .
  2. ECHO ←[13;0;64;13p
  3. COPY CON USRINPUT.TMP
  4. ECHO ←[13;13p
  5. CLS
  6. ECHO You typed:
  7. TYPE USRINPUT.TMP
Note: The character is the Esc character, or ASCII character 27 (or 1B Hexadecimal).
It is a representation of the Esc key.
This Esc character is not to be confused with escape characters!

How to create the Esc character depends on the editor you use:

• In EDIT, use Ctrl+P followed by Alt+27 (press Alt, keep it pressed, and then type 27 on the numeric key pad).
• In Notepad or Wordpad, copy and paste the Escape character from some reference file you can create using EDIT.
• In Norton Commander or File Commander's editor, use Ctrl+Q followed by Alt+27 on the numeric key pad.
• In E and EPM (OS/2) just type Alt+27 on the numeric key pad.
• In UltraEdit, press Ctrl+I followed by the Esc key.

You may have to change the codepage to 437 in some editors.

The previous example is only a bare minimum. The following example not only asks for user input, but stores it in an environment variable USRINPUT as well:

  1. @ECHO OFF
  2. REM * Ask for USeR INPUT and store it in variable USRINPUT
  3. REM * Assumes ANSI.SYS is loaded
  4. REM * Written by Rob van der Woude
  5.  
  6. SET USRINPUT=
  7.  
  8. REM * Turn on ANSI key translation (translate Enter
  9. REM * key to F6+Enter sequence) and ask for input:
  10. ECHO ←[13;0;64;13pEnter one word only . . .
  11.  
  12. REM * Copy entered text to temporary file:
  13. COPY CON %TEMP%.\~USRINP.TMP
  14.  
  15. REM * Turn off ANSI key translation and clear irrelevant screen output:
  16. ECHO ←[13;13p←[3A←[K←[1B←[K←[1B←[K←[2A
  17.  
  18. REM * Add empty line to temporary file. The empty line
  19. REM * will be used to stop DATE asking for new date.
  20. ECHO.>> %TEMP%.\~USRINP.TMP
  21. ECHO.>> %TEMP%.\~USRINP.TMP
  22.  
  23. REM * Create a temporary batch file that will store the
  24. REM * entered text into the environment variable USRINPUT:
  25. TYPE %TEMP%.\~USRINP.TMP | DATE | FIND "):" > %TEMP%.\~USRINP.BAT
  26.  
  27. REM * Create more temporary batch files. Add
  28. REM * more command line parameters if necessary,
  29. REM * as in: ECHO SET USRINPUT=%%3 %%4 %%5 %%6 %%7 %%8 %%9>CURRENT.BAT
  30. ECHO SET USRINPUT=%%3>CURRENT.BAT
  31.  
  32. REM * VOER.BAT and TYP.BAT are replacements for CURRENT.BAT for Dutch
  33. REM * DOS versions; add your own language versions if necessary:
  34. ECHO SET USRINPUT=%%6>VOER.BAT
  35. ECHO SET USRINPUT=%%4>TYP.BAT
  36.  
  37. REM * This temporary batch file now sets the variable USRINPUT:
  38. CALL %TEMP%.\~USRINP.BAT
  39.  
  40. REM * Display the result:
  41. ECHO You typed: ←[1m%USRINPUT%←[0m
  42. ECHO.
  43. PAUSE
  44.  
  45. REM * Finally, clean up the mess of temporary files:
  46. FOR %%A IN (%TEMP%.\~USRINP.BAT %TEMP%.\~USRINP.TMP VOER.BAT TYP.BAT CURRENT.BAT) DO DEL %%A
  47.  
Click to download the ZIPped sources

The previous batch file should work in every DOS version, assuming ANSI.SYS (or one of its replacements, like ANSI.COM) is loaded.
With a few minor adjustments (replace .BAT with .CMD everywhere) it can be used in OS/2 as well. Use READLINE instead, however, in OS/2's DOS sessions.

The following batch file checks if ANSI.SYS is loaded. If so, it will tell the user to press the Enter key only. If not, it will tell the user to press F6 first, followed by the Enter key.
However, to check if ANSI.SYS is loaded, this batch file needs MS-DOS 6 or later.

  1. @ECHO OFF
  2. REM * Asks for USeR INPut and store it in variable USRINPUT
  3. REM * Uses ANSI if available, but works without ANSI too
  4. REM * Assumes MS-DOS 6 or later
  5. REM * Written by Rob van der Woude
  6.  
  7. SET USRINPUT=
  8.  
  9. REM * Check if ANSI sequences can be used (needs at
  10. REM * least MS-DOS 6 to get an errorlevel from FIND):
  11. SET ANSI=1
  12. MEM /C | FIND "ANSI" > NUL
  13. IF ERRORLEVEL 1 SET ANSI=0
  14.  
  15. REM * Turn on ANSI key translation (translate Enter
  16. REM * key to F6 + Enter sequence) if possible:
  17. IF "%ANSI%"=="1" ECHO ←[13;0;64;13p
  18.  
  19. REM * Ask for input:
  20. IF "%ANSI%"=="0" ECHO Enter one word only, and press F6 followed by Enter . . .
  21. IF "%ANSI%"=="1" ECHO Enter one word only, and press Enter . . .
  22.  
  23. REM * Copy entered text to temporary file:
  24. COPY CON %TEMP%.\~USRINP.TMP
  25.  
  26. REM * Turn off ANSI key translation and clear irrelevant screen output:
  27. IF "%ANSI%"=="0" CLS
  28. IF "%ANSI%"=="1" ECHO ←[13;13p←[3A←[K←[1B←[K←[1B←[K←[2A
  29.  
  30. REM * Add empty line to temporary file. The empty line
  31. REM * will be used to stop DATE asking for new date.
  32. ECHO.>> %TEMP%.\~USRINP.TMP
  33. ECHO.>> %TEMP%.\~USRINP.TMP
  34.  
  35. REM * Create a temporary batch file that will store the
  36. REM * entered text into the environment variable USRINPUT:
  37. TYPE %TEMP%.\~USRINP.TMP | DATE | FIND "):" > %TEMP%.\~USRINP.BAT
  38.  
  39. REM * Create more temporary batch files. Add
  40. REM * more command line parameters if necessary,
  41. REM * as in: ECHO SET USRINPUT=%%3 %%4 %%5 %%6 %%7 %%8 %%9>CURRENT.BAT
  42. ECHO SET USRINPUT=%%3>CURRENT.BAT
  43.  
  44. REM * VOER.BAT and TYP.BAT are replacements for CURRENT.BAT for Dutch
  45. REM * DOS versions; add your own language versions if necessary:
  46. ECHO SET USRINPUT=%%6>VOER.BAT
  47. ECHO SET USRINPUT=%%4>TYP.BAT
  48.  
  49. REM * This temporary batch file now sets the variable USRINPUT:
  50. CALL %TEMP%.\~USRINP.BAT
  51.  
  52. REM * Display the result:
  53. IF "%ANSI%"=="0" ECHO You typed: %USRINPUT%
  54. IF "%ANSI%"=="1" ECHO You typed: ←[1m%USRINPUT%←[0m
  55. ECHO.
  56. PAUSE
  57.  
  58. REM * Finally, clean up the mess of temporary files:
  59. FOR %%A IN (%TEMP%.\~USRINP.BAT %TEMP%.\~USRINP.TMP VOER.BAT TYP.BAT CURRENT.BAT) DO DEL %%A
  60. SET ANSI=
  61.  
 
Click to download the ZIPped sources
 

Batch Tools

There are many batch utilities on the web that help ask for user input.
I will list some of them here:

KiXtart

A non-batch solution for Windows 95/98/NT/2000 users is the GetS( ) function in KiXtart:

  1. @ECHO OFF
  2. :: UsrInKix.bat,  Version 1.00 for Win32
  3. :: Batch file using Kix to retreive user input
  4. :: Written by Rob van der Woude
  5. :: http://www.robvanderwoude.com
  6.  
  7. :: Create a temporary Kix script that will
  8. :: in turn create a temporary batch file:
  9. > %TEMP%.\UserIn.kix ECHO REDIRECTOUTPUT( "NUL" )
  10. >>%TEMP%.\UserIn.kix ECHO GETS $UserIn
  11. >>%TEMP%.\UserIn.kix ECHO IF OPEN( 1, "@SCRIPTDIR\UserIn.bat", 5 ) = 0
  12. >>%TEMP%.\UserIn.kix ECHO     WRITELINE( 1, "SET UserIn=" + $UserIn )
  13. >>%TEMP%.\UserIn.kix ECHO ELSE
  14. >>%TEMP%.\UserIn.kix ECHO     ? "Error opening temporary file, errorcode = " + @ERROR
  15. >>%TEMP%.\UserIn.kix ECHO ENDIF
  16. :: Prompt for user input:
  17. ECHO Type anything you like and press Enter when finished:
  18. :: Retreive user input using the Kix script, and
  19. :: then store the result in a temporary batch file:
  20. KIX32.EXE %TEMP%.\UserIn.kix
  21. :: Call the temporary batch file to store
  22. :: the result in an environment variable:
  23. CALL %TEMP%.\UserIn.bat
  24. :: Clean up the temporary files:
  25. IF EXIST %TEMP%.\UserIn.* DEL %TEMP%.\UserIn.*
  26. :: Finaly, display the result:
  27. ECHO You typed: %UserIn%
  28.  
 
Click to download the ZIPped sources
 

OS/2

OS/2 users may want to take a look at UserInPM, a utility written in VX-Rexx, displaying a small PM window asking for user input.
It creates a temporary batch file to set an environment variable to the typed input.
An example batch file, with the resulting PM window:

  1. SET USRINPUT=
  2. USRINPUT.EXE Type whatever you like:
  3. CALL USRINPUT.CMD
  4. ECHO User input: %USRINPUT%
USRINPUT dialogue window

You will need the VX-Rexx runtime library VROBJ.DLL to run UserInPM.

PowerShell

For a login dialog, you can also use the following PowerShell code:

  1. # Dialog asking for credentials
  2. $cred = Get-Credential $username
  3. # Return username and password, delimited by a semicolon
  4. Write-Host $cred.GetNetworkCredential( ).UserName -NoNewline
  5. Write-Host ";" -NoNewline
  6. Write-Host $cred.GetNetworkCredential( ).Password

Save it as login.ps1, and use it to return the user name and password to a batch file:

  1. FOR /F "tokens=1* delims=;" %%A IN ('PowerShell ./login.ps1 %UserName%') DO (
  2. 	SET Usr=%%A
  3. 	SET Pwd=%%B
  4. )

Without a script, a so called "one-liner":

  1. FOR /F "usebackq tokens=1* delims=;" %%A IN (`PowerShell  -Command "$cred = Get-Credential %UserName%; '{0};{1}' -f $cred.GetNetworkCredential( ).UserName, $cred.GetNetworkCredential( ).Password"`) DO (
  2. 	SET Usr=%%A
  3. 	SET Pwd=%%B
  4. )

My LoginDialog.ps1 is based on the code above, with some error handling added:

  1. param(
  2. 	[string]$UserName = $null,
  3. 	[switch]$TabDelimited,
  4. 	[switch]$h,
  5. 	[parameter( ValueFromRemainingArguments = $true )]
  6. 	[object]$invalidArgs
  7. )
  8.  
  9. if ( $h -or $invalidArgs ) {
  10. 	Write-Host
  11. 	Write-Host "LoginDialog.ps1,  Version 1.01"
  12. 	Write-Host "Present a login dialog, and return the user name and password"
  13. 	Write-Host
  14. 	Write-Host "Usage:  " -NoNewline
  15. 	Write-Host "./LoginDialog.ps1  [ username ]  [ -TabDelimited ]" -ForegroundColor White
  16. 	Write-Host
  17. 	Write-Host "Where:  " -NoNewline
  18. 	Write-Host "username           " -ForegroundColor White -NoNewline
  19. 	Write-Host "is the optional user name presented in the dialog"
  20. 	Write-Host "        -TabDelimited      " -ForegroundColor White -NoNewline
  21. 	Write-Host "tab delimited output (default delimiter: semicolon)"
  22. 	Write-Host
  23. 	Write-Host "Written by Rob van der Woude"
  24. 	Write-Host "http://www.robvanderwoude.com"
  25. 	Exit 1
  26. } else {
  27. 	Try
  28. 	{
  29. 		# Dialog asking for credentials
  30. 		$cred = Get-Credential $UserName
  31.  
  32. 		# Return username and password, delimited by a semicolon (default) or tab (switch -TabDelimited)
  33. 		Write-Host $cred.GetNetworkCredential( ).UserName -NoNewline
  34. 		if ( $TabDelimited ) {
  35. 			Write-Host "`t" -NoNewline
  36. 		} else {
  37. 			Write-Host ";" -NoNewline
  38. 		}
  39. 		Write-Host $cred.GetNetworkCredential( ).Password
  40. 	}
  41. 	Catch
  42. 	{
  43. 		Write-Host "-- Canceled --"
  44. 		Exit 1
  45. 	}
  46. }

With LoginDialog.ps1, catching a login error in the batch file is easy:

  1. FOR /F "tokens=1* delims=;" %%A IN ('PowerShell ./LoginDialog.ps1 %UserName%') DO (
  2. 	SET Usr=%%A
  3. 	SET Pwd=%%B
  4. )
  5. IF "%Usr%"=="-- Canceled --" (
  6. 	SET Usr=
  7. 	SET Pwd=
  8. 	ECHO The login was canceled
  9. )

My GetHDDStatusGUI.ps1 combines several GUI techniques:

  1. <#
  2. .SYNOPSIS
  3. Display the SMART status for all local harddisks in a popup window and in Windows' Notification Area (a.k.a. System Tray)
  4.  
  5. .DESCRIPTION
  6. This script uses WMI to query the status of all local physical harddisk drives, and displays the results in a popup window and in Windows' Notification Area (a.k.a. System Tray).
  7. In the status display, each physical harddisk is associated with the drive letter of one of its volumes.
  8. Since Windows 10 limits the number of lines in desktop notifications, any errors will be placed at the top of the displayed status list, otherwise the list is sorted by drive letter.
  9. If all disks report OK, the script returns True and return code ("ErrorLevel") 0, otherwise False and return code 1.
  10. Since this script uses WMI and drive letters, it will not work in Linux.
  11.  
  12. .PARAMETER Modal
  13. Make the popup window always stay on top
  14.  
  15. .PARAMETER Hide
  16. Hide the console window when the script is started, restore it (but minimized) afterwards
  17.  
  18. .PARAMETER Version
  19. Show this script's version number; if combined with -Verbose show full script path, version number and last modified or release date
  20.  
  21. .PARAMETER Debug
  22. Display intermediate results for each disk drive in console; if combined with -Verbose show intermediate results for each associated volume too
  23.  
  24. .PARAMETER Help
  25. Show this script's help screen
  26.  
  27. .OUTPUTS
  28. True and return code 0 if all disks report OK, otherwise False and return code 1
  29.  
  30. .EXAMPLE
  31. . ./GetHDDStatusGUI.ps1
  32. Will display a popup window with the drive letters in use and the SMART status of the associated physical harddisk drive.
  33.  
  34. .EXAMPLE
  35. . ./GetHDDStatusGUI.ps1 -Debug -Verbose
  36. Will list details for all physical harddisk drives and volumes in the console, and display a popup window with the drive letters in use and the SMART status of the associated physical harddisk drive.
  37.  
  38. .EXAMPLE
  39. . ./GetHDDStatusGUI.ps1 -Version -Verbose
  40.  
  41. Will display this script's full path, version number and last modified or release date.
  42.  
  43. .LINK
  44. Script written by Rob van der Woude:
  45. https://www.robvanderwoude.com/
  46.  
  47. .LINK
  48. Disk check based on code by Geoff:
  49. http://www.uvm.edu/~gcd/2013/01/which-disk-is-that-volume-on
  50.  
  51. .LINK
  52. System Tray ToolTip Balloon code by Don Jones:
  53. http://blog.sapien.com/current/2007/4/27/creating-a-balloon-tip-notification-in-powershell.html
  54.  
  55. .LINK
  56. Extract icons from Shell32.dll by Thomas Levesque:
  57. http://stackoverflow.com/questions/6873026
  58.  
  59. .LINK
  60. Hide and restore console by Anthony:
  61. http://stackoverflow.com/a/15079092
  62.  
  63. .LINK
  64. Capture common parameters by mklement0:
  65. https://stackoverflow.com/a/48643616
  66. #>
  67.  
  68. param (
  69. 	[parameter( ValueFromRemainingArguments = $true )]
  70. 	[string[]]$Args, # Leave all argument validation to the script, not to PowerShell
  71. 	[switch]$Modal,
  72. 	[switch]$Hide,
  73. 	[switch]$Version,
  74. 	[switch]$Help
  75. )
  76.  
  77. $progver = "1.04"
  78.  
  79. [bool]$Debug = ( $PSBoundParameters.ContainsKey( 'Debug' ) )
  80. [bool]$Verbose = ( $PSBoundParameters.ContainsKey( 'Verbose' ) )
  81.  
  82. # Show help if any unnamed argument is given
  83. $Help = $Help -or ( $Args.Length -gt 0 )
  84.  
  85. # Show help if running in Linux
  86. $Help = $Help -or ( $HOME[0] -eq '/' )
  87.  
  88. # Help disables Hide parameter
  89. $Hide = $Hide -and -not $Help
  90.  
  91. # Debug disables Hide parameter
  92. $Hide = $Hide -and -not $Debug
  93.  
  94. if ( $Help ) {
  95. 	Get-Help "$PSCommandPath" -Full
  96. 	exit 1
  97. }
  98.  
  99. if ( $Version ) {
  100. 	if ( $Verbose ) {
  101. 		$lastmod = ( [System.IO.File]::GetLastWriteTime( $PSCommandPath ) )
  102. 		if ( $lastmod.ToString( "h.mm" ) -eq $progver ) {
  103. 			"`"{0}`", Version {1}, release date {2}" -f $PSCommandPath, $progver, $lastmod.ToString( "yyyy-MM-dd" )
  104. 		} else {
  105. 			# if last modified time is not equal to program version, the script has been tampered with
  106. 			"`"{0}`", Version {1}, last modified date {2}" -f $PSCommandPath, $progver, $lastmod.ToString( "yyyy-MM-dd" )
  107. 		}
  108. 	} else {
  109. 		$progver
  110. 	}
  111. 	exit 0
  112. }
  113.  
  114. #######################################
  115. # Hide console window                 #
  116. # by Anthony on StackOverflow.com     #
  117. # http://stackoverflow.com/a/15079092 #
  118. #######################################
  119.  
  120. $signature1 = @'
  121. public static void ShowConsoleWindow( int state )
  122. {
  123. 	var handle = GetConsoleWindow( );
  124. 	ShowWindow( handle, state );
  125. }
  126.  
  127. [System.Runtime.InteropServices.DllImport( "kernel32.dll" )]
  128. static extern IntPtr GetConsoleWindow( );
  129.  
  130. [System.Runtime.InteropServices.DllImport( "user32.dll" )]
  131. static extern bool ShowWindow( IntPtr hWnd, int nCmdShow );
  132. '@
  133.  
  134. $hideconsole = Add-Type -MemberDefinition $signature1 -Name Hide -Namespace HideConsole -ReferencedAssemblies System.Runtime.InteropServices -PassThru
  135.  
  136. # Hide console
  137. if ( $Hide ) {
  138. 	$hideconsole::ShowConsoleWindow( 0 )
  139. }
  140.  
  141. ##################
  142. # Disk inventory #
  143. ##################
  144.  
  145. [System.Collections.SortedList]$volumedetails = New-Object System.Collections.SortedList
  146. [System.Collections.SortedList]$volumestatus  = New-Object System.Collections.SortedList
  147.  
  148. if ( $Debug ) {
  149. 	Write-Host "Disk#`tStatus`tSize (GB)`tModel"
  150. 	Write-Host "=====`t======`t=========`t====="
  151. }
  152.  
  153. $diskdrives = Get-WmiObject -Namespace "root/CIMV2" -Class Win32_DiskDrive
  154. $warnings   = $false
  155. foreach ( $disk in $diskdrives ) {
  156. 	$diskindex  = $disk.Index
  157. 	$diskmodel  = $disk.Model -replace "Disk Device","Disk"
  158. 	$disksize   = "{0,5:F0} GB" -f ( $disk.Size / 1GB )
  159. 	$diskstatus = $disk.Status
  160. 	if ( $Debug ) {
  161. 		if ( $Verbose ) {
  162. 			Write-Host
  163. 		}
  164. 		Write-Host ( "{0}`t{1}`t{2}`t{3}" -f $diskindex, $diskstatus, $disksize, $diskmodel )
  165. 	}
  166. 	$part_query = 'ASSOCIATORS OF {Win32_DiskDrive.DeviceID="' + $disk.DeviceID.replace('\','\\') + '"} WHERE AssocClass=Win32_DiskDriveToDiskPartition'
  167. 	$partitions = @( Get-WmiObject -Query $part_query | Sort-Object StartingOffset )
  168. 	foreach ( $partition in $partitions ) {
  169. 		$vol_query = 'ASSOCIATORS OF {Win32_DiskPartition.DeviceID="' + $partition.DeviceID + '"} WHERE AssocClass=Win32_LogicalDiskToPartition'
  170. 		$volumes   = @( Get-WmiObject -Query $vol_query )
  171. 		foreach ( $volume in $volumes ) {
  172. 			# 0 = Unknown; 1 = No Root Directory; 2 = Removable Disk; 3 = Local Disk; 4 = Network Drive; 5 = Compact Disc; 6 = RAM Disk
  173. 			# DriveType 3 means harddisks only
  174. 			if ( $volume.DriveType -eq 3 ) {
  175. 				if ( $Debug -and $Verbose ) {
  176. 					Write-Host ( "{0}`t{1,2}`t{2}`t{3}" -f $diskindex, $volume.Name, $disksize, $diskmodel )
  177. 				}
  178. 				if ( -not $volumedetails.Contains( $volume.Name ) ) {
  179. 					$volumedetails.Add( $volume.Name, "[Disk {0,2}]  {1}  {2}" -f ( $diskindex, $disksize, $diskmodel ) )
  180. 					$volumestatus.Add( $volume.Name, $diskstatus )
  181. 				}
  182. 			}
  183. 		}
  184. 	}
  185. }
  186.  
  187.  
  188. #################
  189. # Dialog window #
  190. #################
  191.  
  192. Add-Type -AssemblyName System.Windows.Forms
  193.  
  194. [System.Windows.Forms.Application]::EnableVisualStyles( )
  195.  
  196. $form = New-Object System.Windows.Forms.Form
  197. $form.Width           = 640
  198. $form.Height          = 25 * $volumedetails.Count + 120
  199. $form.Font            = 'Courier New, 10'
  200. $form.BackColor       = 'White'
  201. $form.MaximizeBox     = $false;
  202. $form.FormBorderStyle = 'FixedSingle'
  203. $form.TopMost         = $Modal
  204.  
  205. $y    = 0
  206. $even = $false
  207. $volumedetails.Keys | ForEach-Object {
  208. 	$label           = New-Object System.Windows.Forms.Label
  209. 	$label.Size      = '35, 20'
  210. 	$label.Location  = New-Object System.Drawing.Point( 10, ( 15 + $y ) )
  211. 	$label.Text      = "$_"
  212. 	if ( $even ) { $label.BackColor = 'ButtonFace' }
  213. 	$form.Controls.Add( $label )
  214.  
  215. 	$status          = ( $volumestatus[$_] )
  216. 	$label           = New-Object System.Windows.Forms.Label
  217. 	$label.Size      = '40, 20 '
  218. 	$label.Location  = New-Object System.Drawing.Point( 45, ( 15 + $y ) )
  219. 	$label.Text      = $status
  220. 	if ( $status -eq "OK" ) {
  221. 		$label.ForeColor = 'Green'
  222. 	} else {
  223. 		$label.ForeColor = 'Red'
  224. 		$warnings        = $true
  225. 	}
  226. 	if ( $even ) { $label.BackColor = 'ButtonFace' }
  227. 	$form.Controls.Add( $label )
  228.  
  229. 	$label           = New-Object System.Windows.Forms.Label
  230. 	$label.Size      = '490, 20'
  231. 	$label.Location  = New-Object System.Drawing.Point( 85, ( 15 + $y ) )
  232. 	$label.Text      = $volumedetails[$_]
  233. 	if ( $even ) { $label.BackColor = 'ButtonFace' }
  234. 	$form.Controls.Add( $label )
  235.  
  236. 	$y    = $y + 25
  237. 	$even = -not $even
  238. }
  239.  
  240. if ( $warnings ) {
  241. 	$form.Text = "HDD Status Warning"
  242. } else {
  243. 	$form.Text = "HDD Status OK"
  244. }
  245.  
  246. $buttonOK           = New-Object System.Windows.Forms.Button
  247. $buttonOK.BackColor = 'ButtonFace'
  248. $buttonOK.Text      = "OK"
  249. $buttonOK.Size      = '60,24'
  250. $buttonOK.Location  = New-Object System.Drawing.Point( 85, ( 30 + $y ) )
  251. $form.Controls.Add( $buttonOK )
  252. $form.AcceptButton  = $buttonOK # Pressing Enter  closes the dialog
  253. $form.CancelButton  = $buttonOK # Pressing Escape closes the dialog
  254.  
  255.  
  256. ########################################
  257. # System tray balloon tip notification #
  258. ########################################
  259.  
  260. [System.Windows.Forms.ToolTipIcon]$icon = [System.Windows.Forms.ToolTipIcon]::Info
  261. $title = "HDD Status OK"
  262. $systraymessage = ""
  263. $volumedetails.Keys | ForEach-Object {
  264. 	$status = ( $volumestatus[$_] )
  265. 	if ( $status -eq "OK" ) {
  266. 		$systraymessage = $systraymessage + "$_`t$status`n" # list in alphabetical sort order
  267. 	} else {
  268. 		$systraymessage = "$_`t$status`n$systraymessage" # errors at top of list
  269. 		$icon = [System.Windows.Forms.ToolTipIcon]::Error
  270. 		$title = "Warning: HDD Errors"
  271. 	}
  272. }
  273.  
  274.  
  275. ################################################################
  276. # Extract system tray icon from Shell32.dll                    #
  277. # C# code to extract icons from Shell32.dll by Thomas Levesque #
  278. # http://stackoverflow.com/questions/6873026                   #
  279. ################################################################
  280.  
  281. $signature2 = @'
  282. [DllImport( "Shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall )]
  283. private static extern int ExtractIconEx( string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons );
  284.  
  285. public static Icon Extract( string file, int number, bool largeIcon )
  286. {
  287. 	IntPtr large;
  288. 	IntPtr small;
  289. 	ExtractIconEx( file, number, out large, out small, 1 );
  290. 	try
  291. 	{
  292. 		return Icon.FromHandle( largeIcon ? large : small );
  293. 	}
  294. 	catch
  295. 	{
  296. 		return null;
  297. 	}
  298. }
  299. '@
  300.  
  301. $iconextractor = Add-Type -MemberDefinition $signature2 -Name IconExtract -Namespace IconExtractor -ReferencedAssemblies System.Windows.Forms,System.Drawing -UsingNamespace System.Windows.Forms,System.Drawing -PassThru
  302.  
  303. # System tray icon depends on status
  304. if( $title -eq "HDD Status OK" ) {
  305. 	$systrayicon = $iconextractor::Extract( "C:\Windows\System32\shell32.dll", 223, $true )
  306. } else {
  307. 	$systrayicon = $iconextractor::Extract( "C:\Windows\System32\shell32.dll", 53, $true )
  308. }
  309.  
  310. # Show system tray icon and balloon tip
  311. $notify = New-Object System.windows.Forms.NotifyIcon
  312. $notify.BalloonTipText = $systraymessage
  313. $notify.BalloonTipTitle = $title
  314. $notify.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]$icon
  315. $notify.Icon = $systrayicon
  316. $notify.Visible = $true
  317. $notify.ShowBalloonTip( 30000 )
  318.  
  319. # Show dialog
  320. [void] $form.ShowDialog( )
  321.  
  322.  
  323. ##########################################
  324. # Restore console minimized (2)          #
  325. # Change to 1 to restore to normal state #
  326. ##########################################
  327.  
  328. if ( $Hide ) {
  329. 	$hideconsole::ShowConsoleWindow( 2 )
  330. }
  331.  
  332. #################################
  333. # Exit code 1 in case of errors #
  334. #################################
  335.  
  336. if ( $warnings ) {
  337. 	$false
  338. 	exit 1
  339. } else {
  340. 	$true
  341. 	exit 0
  342. }
  343.  

 

 

 

PowerShell on-the-fly

It is possible to write batch files using PowerShell without requiring a (temporary) PowerShell script.
This is where PowerShell's -Command switch comes in handy.
However, don't expect it to make your code readable, and maintenance will be a real nightmare.

The messagebox below is a fairly simple example:

  1. FOR /F "usebackq" %%A IN (`Powershell.exe -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show( 'Are you sure you want to continue?', 'Be Careful', [System.Windows.Forms.MessageBoxButtons]::YesNoCancel, [System.Windows.Forms.MessageBoxIcon]::Exclamation, [System.Windows.Forms.MessageBoxDefaultButton]::Button2, [System.Windows.Forms.MessageBoxOptions]::ServiceNotification )"`) DO SET Answer=%%A
Notes: 1. The code above is wrapped to allow a quick overview, but should be entered as a single line in a batch file.
2. Use single quotes for strings inside the PowerShell code, and backquotes for the batch file's FOR command.
3. Do not use backquotes for PowerShell literals!
If you have to use PowerShell literals, enter them as [char] as shown in the following code:
  1. FOR /F "usebackq" %%A IN (`Powershell.exe -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show( ( 'Are you sure you want to continue?{0}Really, really sure?' -f [char]0x0D ), 'Be Careful', [System.Windows.Forms.MessageBoxButtons]::YesNoCancel, [System.Windows.Forms.MessageBoxIcon]::Exclamation, [System.Windows.Forms.MessageBoxDefaultButton]::Button2, [System.Windows.Forms.MessageBoxOptions]::ServiceNotification )"`) DO SET Answer=%%A

( 'Are you sure you want to continue?{0}Really, really sure?' -f [char]0x0D ) instead of 'Are you sure you want to continue?`nReally, really sure?'

 

OK, even simpler, a plain popup message:

  1. Powershell.exe -Command "Add-Type -AssemblyName System.Windows.Forms; [void] [System.Windows.Forms.MessageBox]::Show( 'Just a popup message', 'Message' )"

 

One more example, using the code of our previous PowerShell login dialog example:

  1. FOR /F "usebackq tokens=1* delims=;" %%A IN (`PowerShell.exe -Command "$cred = Get-Credential %UserName%; Write-Host ( '{0};{1}' -f $cred.GetNetworkCredential( ).UserName, $cred.GetNetworkCredential( ).Password )"`) DO (SET Login.Name=%%A&SET Login.Passwd=%%B)
Notes: 1. The code above is wrapped to allow a quick overview, but should be entered as a single line in a batch file.
2. Credentials are stored in the environment variables Login.Name and Login.Passwd

 

VBScript and Internet Explorer (discontinued in 2022)

Advanced dialog boxes, output and input, including (masked) password prompts, can also be created using VBScript and Internet Explorer, but I would not recommend creating them on the fly.

Sample change password dialog created using VBScript and Internet Explorer

See the VBScript Scripting Techniques section for some of these advanced user message windows.

Note: For peace of mind, I would recommend not using this technique at all: every now and then a Windows/IE update and/or too "tight" security settings will break the code, forcing you to rewrite it.
My "educated guess" is that PowerShell code as shown above will last longer.
Update: On June 15, 2022, Microsoft ended support for Internet Explorer, and announced that it would be removed in a future Windows Update.
Do not use this technique any longer.

page last modified: 2022-06-29; loaded in 0.0614 seconds