0

as you can see guys I'm trying to send Email with an attachment. everything working fine until we come to the message body content. the body contains the hard drives partitions caption. as you can see from the command

for /f "skip=1 delims=" %%x in ('wmic logicaldisk get caption') do set mm=%%x

but it always arrive empty.

what could be the problem ?

@ECHO OFF
REM https://stackoverflow.com/questions/28605803/can-not-send-mail-using-smtp-gmail-com-port-587-from-vbs-script/28606754#28606754
Title Sending E-Mail with Gmail Less Secure Applications using Powershell and Batch

SET GmailAccount="user@gmail.com"
SET GmailPassword="password"
SET Attachment="d:\test\myFile.txt"
REM We write our Powershell script 
for /f "skip=1 delims=" %%x in ('wmic logicaldisk get caption') do set mm=%%x
CALL :WritePS
REM We execute our Powershell script .PS1 by passing arguments from the command line or a batch file
Powershell -ExecutionPolicy bypass -noprofile -file "%PSScript%" "%GmailAccount%" "%GmailPassword%" "%Attachment%"
IF EXIST "%PSScript%" DEL /Q /F "%PSScript%"
pause
EXIT
REM -----------------------------------------------------------------------------------------------------
:WritePS
SET PSScript=%temp%\temp_SendeMail.ps1
> "%PSScript%" (
    ECHO $Username  = $args[0]
    ECHO $EmailPassword = $args[1]
    ECHO $Attachment= $args[2]
    ECHO $EmailTo = "tomytraget@gmail.com"
    ECHO $EmailFrom  = $Username
    ECHO $Subject = "Paths"   
    ECHO $Body= "%mm%"  
    ECHO $SMTPServer  = "smtp.gmail.com"  
    ECHO $SMTPMessage = New-Object System.Net.Mail.MailMessage($EmailFrom, $EmailTo, $Subject, $Body^) 
    ECHO $Attachment  = New-Object System.Net.Mail.Attachment($Attachment^)
    ECHO $SMTPMessage.Attachments.Add($Attachment^)
    ECHO $SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 587^)
    ECHO $SMTPClient.EnableSsl = $true
    ECHO $SMTPClient.Credentials = New-Object System.Net.NetworkCredential($Username, $EmailPassword^) 
    ECHO $SMTPClient.Send($SMTPMessage^)
)
Exit /B
REM -----------------------------------------------------------------------------------------------------
Deller
  • 29
  • 7
  • 2
    because the last line is an empty line (not actually empty, it's a Carriage Return), so `mm` is set to `` – ScriptKidd Jun 14 '20 at 04:44
  • 2
    I suggest you do the entire thing in Powershell. – Nick.Mc Jun 14 '20 at 04:44
  • 2
    what do you expect anyway? the ***last*** drive? PPS `wmic` does weird things with it's Unicode output in a `FOR /F` loop – ScriptKidd Jun 14 '20 at 04:46
  • I just want the solution, what i should do or change ? – Deller Jun 14 '20 at 04:46
  • 1
    Does this answer your question? [cmd: save wmic output to variable](https://stackoverflow.com/questions/48674006/cmd-save-wmic-output-to-variable) – SomethingDark Jun 14 '20 at 04:49
  • 2
    It is probably easier to run cmd commands in powershell than it is to run powershell commands in cmd for your case... I would recommend doing the whole thing in powershell... A lot of things are probably getting lost in translation as of your script now... – Nico Nekoru Jun 14 '20 at 04:49
  • Can you provide some example input and output for this? This may make it easier to help you with... – Nico Nekoru Jun 14 '20 at 04:52
  • Your `for /f` command is not doing what you require of it, regardless of the 'Unicode' output issue! It will define `%mm%` with value content matching the result of the last driveletter, _('Caption')_, returned by WMIC. That will inevitably be that which will be alphabetically closest to the letter `Z`. – Compo Jun 14 '20 at 11:24
  • `FOR /F` skips lines which are 1. empty 2. start with `EOL` 3. greater than 8191 bytes excluding delimiters But the last line isn't actually "empty" because of the trailing ``, that's why `mm` is set to `` – ScriptKidd Jun 25 '20 at 22:50

2 Answers2

1

As I mentioned in the comment section, your existing method would only result in a variable value consisting of the last drive letter output from the command. You would need to join those results into a single string value.

To retrieve the information you require, there's no real need to use in your .

For example:

Set "mm=Mounted drive letters"
For /F "Delims=\" %%G In ('%__AppDir__%mountvol.exe^|%__AppDir__%find.exe ":\"')Do For %%H In (%%G)Do Call Set "mm=%%mm%% %%H"


If you really do want to use , you can retrieve your information without having to worry about the Unicode output, mentioned in the comments and the other answer.

If you're using /, or above, perhaps this will work better for you:

Set "mm=Mounted drive letters"
For /F EOL^=DTokens^=2Delims^=^" %%G In ('%__AppDir__%wbem\WMIC.exe Path Win32_MountPoint')Do Call Set "mm=%%mm%% %%G"

Otherwise, you could still use with Win32_LogicalDisk

Set "mm=Mounted drive letters"
For /F Delims^=: %%G In ('"%__AppDir__%wbem\WMIC.exe LogicalDisk Get DeviceID 2>NUL|%__AppDir__%find.exe ":""')Do Call Set "mm=%%mm%% %%G:"


However, as already mentioned in the comments, it probably makes more sense just to retrieve the information you need as part of your script:

For PowerShell versions 1.0 and 2.0

$Body = ( Get-WMIObject -Class Win32_LogicalDisk | Select-Object -ExpandProperty DeviceID ) -Join ' '

To do that from your existing , you need to delete your for /f line, and replace ECHO $Body= "%mm%", escaping the pipe character and the closing parenthesis, e.g.:

ECHO $Body = ( Get-WMIObject -Class Win32_LogicalDisk ^| Select-Object -ExpandProperty DeviceID ^) -Join ' '

Or for PowerShell version 3.0 and above.

$Body = ( Get-CIMInstance -ClassName Win32_LogicalDisk | Select-Object -ExpandProperty DeviceID ) -Join ' '

…and to do that from your existing , once again, remove your for /f line, and instead of ECHO $Body= "%mm%", use:

ECHO $Body = ( Get-CIMInstance -ClassName Win32_LogicalDisk ^| Select-Object -ExpandProperty DeviceID ^) -Join ' '


It was unclear from your question whether you were specifically looking for local disks, as you mentioned hard drives.

If you're only looking for local disks, you could still use Win32_LogicalDisk, and filter by DriveType, (3Local Disk).

Using from your

Set "mm=Mounted drive letters"
For /F Delims^=: %%G In ('"%__AppDir__%wbem\WMIC.exe LogicalDisk Where (DriveType=3) Get DeviceID 2>NUL|%__AppDir__%find.exe ":""')Do Call Set "mm=%%mm%% %%G:"

Directly in and :

$Body = ( Get-WMIObject -Class Win32_LogicalDisk -Filter "DriveType = '3'" | Select-Object -ExpandProperty DeviceID ) -Join ' '

To do that from your existing , delete your for /f line, and replace ECHO $Body= "%mm%" with:

ECHO $Body = ( Get-WMIObject -Class Win32_LogicalDisk  -Filter "DriveType = '3'" ^| Select-Object -ExpandProperty DeviceID ^) -Join ' '

Or directly in and above.

$Body = ( Get-CIMInstance -ClassName Win32_LogicalDisk -Filter "DriveType = '3'" | Select-Object -ExpandProperty DeviceID ) -Join ' '

…and once again from your , remove your for /f line, and replace ECHO $Body= "%mm%" with:

ECHO $Body = ( Get-CIMInstance -ClassName Win32_LogicalDisk -Filter "DriveType = '3'" ^| Select-Object -ExpandProperty DeviceID ^) -Join ' '
Compo
  • 36,585
  • 5
  • 27
  • 39
0

The command wmic logicaldisk get caption outputs on my Windows PC:

Caption  
C:       
D:       
E:       
F:       
R:       

This is a Unicode output which is UTF-16 Little Endian encoded with byte order mark (BOM) and not ASCII/ANSI/OEM encoded as expected by Windows command processor cmd.exe containing internal command FOR.

The output data is in binary:

0000h: FF FE 43 00 61 00 70 00 74 00 69 00 6F 00 6E 00 ; ÿþC.a.p.t.i.o.n.
0010h: 20 00 20 00 0D 00 0A 00 43 00 3A 00 20 00 20 00 ;  . .....C.:. . .
0020h: 20 00 20 00 20 00 20 00 20 00 0D 00 0A 00 44 00 ;  . . . . .....D.
0030h: 3A 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 ; :. . . . . . . .
0040h: 0D 00 0A 00 45 00 3A 00 20 00 20 00 20 00 20 00 ; ....E.:. . . . .
0050h: 20 00 20 00 20 00 0D 00 0A 00 46 00 3A 00 20 00 ;  . . .....F.:. .
0060h: 20 00 20 00 20 00 20 00 20 00 20 00 0D 00 0A 00 ;  . . . . . .....
0070h: 52 00 3A 00 20 00 20 00 20 00 20 00 20 00 20 00 ; R.:. . . . . . .
0080h: 20 00 0D 00 0A 00                               ;  .....

Data representation format: hexadecimal file offset: data bytes with hexadecimal values ; ASCII representation using code page Windows-1252.

The command FOR expects the text to process character encoded with one byte per character and not UTF-16 LE encoded. So it expects the data as:

0000h: 43 61 70 74 69 6F 6E 20 20 0D 0A 43 3A 20 20 20 ; Caption  ..C:   
0010h: 20 20 20 20 0D 0A 44 3A 20 20 20 20 20 20 20 0D ;     ..D:       .
0020h: 0A 45 3A 20 20 20 20 20 20 20 0D 0A 46 3A 20 20 ; .E:       ..F:  
0030h: 20 20 20 20 20 0D 0A 52 3A 20 20 20 20 20 20 20 ;      ..R:       
0040h: 0D 0A                                           ; ..

The captured text with wrong character encoding is not correct processed by FOR.

It is unclear what should be assigned to environment variable mm from such an output. If the drive letter without colon of last drive should be assigned to mm, I recommend to use following command line:

for /F "tokens=2 delims=:=" %%I in ('%SystemRoot%\System32\wbem\wmic.exe LOGICALDISK GET Caption /VALUE') do set "mm=%%I"

The additional option /VALUE changes the output to:



Caption=C:


Caption=D:


Caption=E:


Caption=F:


Caption=R:


The binary data for this output is:

0000h: FF FE 0D 00 0A 00 0D 00 0A 00 43 00 61 00 70 00 ; ÿþ........C.a.p.
0010h: 74 00 69 00 6F 00 6E 00 3D 00 43 00 3A 00 0D 00 ; t.i.o.n.=.C.:...
0020h: 0A 00 0D 00 0A 00 0D 00 0A 00 43 00 61 00 70 00 ; ..........C.a.p.
0030h: 74 00 69 00 6F 00 6E 00 3D 00 44 00 3A 00 0D 00 ; t.i.o.n.=.D.:...
0040h: 0A 00 0D 00 0A 00 0D 00 0A 00 43 00 61 00 70 00 ; ..........C.a.p.
0050h: 74 00 69 00 6F 00 6E 00 3D 00 45 00 3A 00 0D 00 ; t.i.o.n.=.E.:...
0060h: 0A 00 0D 00 0A 00 0D 00 0A 00 43 00 61 00 70 00 ; ..........C.a.p.
0070h: 74 00 69 00 6F 00 6E 00 3D 00 46 00 3A 00 0D 00 ; t.i.o.n.=.F.:...
0080h: 0A 00 0D 00 0A 00 0D 00 0A 00 43 00 61 00 70 00 ; ..........C.a.p.
0090h: 74 00 69 00 6F 00 6E 00 3D 00 52 00 3A 00 0D 00 ; t.i.o.n.=.R.:...
00a0h: 0A 00 0D 00 0A 00 0D 00 0A 00                   ; ..........

FOR splits up each non-empty line into substrings using equal sign and colon as delimiters because of option delims=:=. Therefore the last data line is split up into the substrings Caption and R.

The option tokens=2 informs command FOR that the second substring should be assigned to the specified loop variable I which is for all lines with data the drive letter. The command SET is executed only if the line has at least two substrings after splitting the line up and so there is something assigned to the loop variable I.

This solution works even on FOR interpreting the last two empty lines of Unicode output wrong as 0D 0D 0A (carriage return, carriage return, line-feed). The single carriage return is in this case the first and only substring. There is no second substring. So the wrong interpreted empty lines do not result with tokens=2 in running command SET with assigning nothing to it as the carriage return assigned to loop variable I is ignored by command SET which would result in undefinition of the environment variable mm.

This command line assigns the volume name (drive label) up to first space of the last drive with a volume name to environment variable mm (awful variable name).

for /F eol^= %%I in ('%SystemRoot%\System32\wbem\wmic.exe LOGICALDISK GET VolumeName ^| %SystemRoot%\System32\findstr.exe /R /V /C:"^ *$" /C:"^VolumeName *$"') do set "mm=%%I"

Note: NTFS drives can have a space in volume name. So the string assigned to environment variable mm is not correct on last drive having a space in its volume name.

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.

  • echo /?
  • findstr /?
  • for /?
  • set /?
  • wmic /?
  • wmic logicaldisk /?
  • wmic logicaldisk get /?

See also the Microsoft documentation for Win32_LogicalDisk class.

Mofi
  • 46,139
  • 17
  • 80
  • 143