Почему данные wmi c из текстового файла не могут быть обработаны из-за какой-то странной проблемы кодирования символов? - PullRequest
2 голосов
/ 04 апреля 2020

Я работал над небольшим скриптом, который определяет, какой диск можно использовать в нашей указанной c системе. На диске должно быть меньше 1 ТБ места, чтобы его можно было использовать.

Это полный код:

@echo off

chcp 65001>nul
setlocal enabledelayedexpansion

wmic logicaldisk get caption,freespace>c:\cmd\1\getDiskInfo.txt

for /f "tokens=1,2 eol=C" %%I in (C:\cmd\1\getDiskInfo.txt) do (
    set diskCaption=%%I
    set diskFreeSpace=%%J
    set captionFreeSpace=!diskCaption! !diskFreeSpace!

    for /f "tokens=1,2 delims= " %%X in ("!captionFreeSpace!") do (
        if [%%Y] neq [] set usedDisks=%%X %%Y

        for /f "tokens=1,2 delims= " %%A in ("!usedDisks!") do (
            set freeSpaceFirstChar=%%B
            set /a freeSpaceFirstChar=!freeSpaceFirstChar:~0,1!

            if !freeSpaceFirstChar! gtr 1 set usableDisk=%%A
        )
    )
)

echo %usableDisk%

pause

Но вывод, который я получаю для %usableDisk% всегда ECHO is off., что означает, что %usableDisk% даже не существует. Я провел небольшое расследование, и, насколько я понимаю, это связано с проблемой кодировки символов. Я скопировал содержимое getDiskInfo.txt в другой файл .txt, и пакетный файл смог дать мне правильный вывод с этим созданным мной текстовым файлом. Содержимое getDiskInfo.txt и другого текстового файла было равно:

Caption  FreeSpace      
A:                      
B:       1098552672256  
C:       40824201216    
D:                      
E:       1042498560000  
F:       40222941184    

Вывод исходного файла, созданного сценарием, был ECHO is off.. Вывод с созданным мной текстовым файлом был F:, что является правильным выводом, потому что мы не можем использовать системный диск C:.

Поэтому я попытался echo END OF FILE>>getDiskInfo.txt, и он добавил строку 久⁄䙏䘠䱉൅ в файл, созданный скриптом, но эта же команда добавила END OF FILE в мой текстовый файл. Я полностью потерян на этом.

У вас есть какие-либо предложения или возможные решения?

Ответы [ 2 ]

1 голос
/ 04 апреля 2020

Для решения этой задачи необходимо решить несколько проблем, чтобы получить букву диска (локального) жесткого диска с менее чем одним TiB (1 099 511 627 776 байт) свободным пространством, которое не является системный диск.

1. Кодировка символов WMI C вывод

WMI C выводит данные всегда с кодировкой UTF-16 Little Endian с меткой порядка байтов , сокращенно UTF-16LE + BOM .

Таким образом, вывод данных

Caption  FreeSpace      
A:                      
B:       1098552672256  
C:       40824201216    
D:                      
E:       1042498560000  
F:       40222941184    

является потоком байтов со смещением байта влево на : и представлением ASCII вправо на ;:

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 46 00 72 00 65 00 65 00 53 00 70 00 ;  . .F.r.e.e.S.p.
0020h: 61 00 63 00 65 00 20 00 20 00 20 00 20 00 20 00 ; a.c.e. . . . . .
0030h: 20 00 0D 00 0A 00 41 00 3A 00 20 00 20 00 20 00 ;  .....A.:. . . .
0040h: 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 ;  . . . . . . . .
0050h: 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 ;  . . . . . . . .
0060h: 20 00 20 00 20 00 0D 00 0A 00 42 00 3A 00 20 00 ;  . . .....B.:. .
0070h: 20 00 20 00 20 00 20 00 20 00 20 00 31 00 30 00 ;  . . . . . .1.0.
0080h: 39 00 38 00 35 00 35 00 32 00 36 00 37 00 32 00 ; 9.8.5.5.2.6.7.2.
0090h: 32 00 35 00 36 00 20 00 20 00 0D 00 0A 00 43 00 ; 2.5.6. . .....C.
00a0h: 3A 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 ; :. . . . . . . .
00b0h: 34 00 30 00 38 00 32 00 34 00 32 00 30 00 31 00 ; 4.0.8.2.4.2.0.1.
00c0h: 32 00 31 00 36 00 20 00 20 00 20 00 20 00 0D 00 ; 2.1.6. . . . ...
00d0h: 0A 00 44 00 3A 00 20 00 20 00 20 00 20 00 20 00 ; ..D.:. . . . . .
00e0h: 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 ;  . . . . . . . .
00f0h: 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 ;  . . . . . . . .
0100h: 20 00 0D 00 0A 00 45 00 3A 00 20 00 20 00 20 00 ;  .....E.:. . . .
0110h: 20 00 20 00 20 00 20 00 31 00 30 00 34 00 32 00 ;  . . . .1.0.4.2.
0120h: 34 00 39 00 38 00 35 00 36 00 30 00 30 00 30 00 ; 4.9.8.5.6.0.0.0.
0130h: 30 00 20 00 20 00 0D 00 0A 00 46 00 3A 00 20 00 ; 0. . .....F.:. .
0140h: 20 00 20 00 20 00 20 00 20 00 20 00 34 00 30 00 ;  . . . . . .4.0.
0150h: 32 00 32 00 32 00 39 00 34 00 31 00 31 00 38 00 ; 2.2.2.9.4.1.1.8.
0160h: 34 00 20 00 20 00 20 00 20 00 0D 00 0A 00       ; 4. . . . .....

Но Windows командный процессор ожидает один байт на кодировку символов с использованием кодовой страницы в качестве вывод при запуске в открывшемся окне командной строки команды chcp. Кодовая страница зависит от того, какая страна настроена для учетной записи, используемой для запуска командного процесса, обрабатывающего командный файл.

Командная строка chcp 65001>nul для перехода на кодировку Unicode UTF-8 здесь никакой помощи.

Обработка вывода в кодировке UTF-16LE с помощью FOR напрямую вызывает проблемы, как описано несколько раз в документации по переполнению стека, см., например, Как исправить неправильное поведение при перезаписи переменных при разборе output?

Решением было бы перенаправить вывод WMI C во временный файл, вывести этот временный файл для обработки STDOUT (стандартно вывод) командного процесса, запущенного в фоновом режиме с помощью %ComSpec% /c с использованием команды TYPE с захватом этого вывода командным процессом, выполняющим пакетный файл, обработайте этот вывод ASCII построчно и, наконец, удалите временный файл .

@echo off
setlocal EnableExtensions DisableDelayedExpansion
%SystemRoot%\System32\wbem\wmic.exe LOGICALDISK GET Caption,FreeSpace >"%TEMP%\%~n0.tmp"
if not exist "%TEMP%\%~n0.tmp" goto EndBatch

for /F "skip=1 tokens=1,2" %%I in ('type "%TEMP%\%~n0.tmp"') do echo %%I %%J
del "%TEMP%\%~n0.tmp"

:EndBatch
endlocal

В этом случае FOR обрабатывает поток байтов ASCII:

000h: 43 61 70 74 69 6F 6E 20 20 46 72 65 65 53 70 61 ; Caption  FreeSpa
010h: 63 65 20 20 20 20 20 20 0D 0A 41 3A 20 20 20 20 ; ce      ..A:    
020h: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 ;                 
030h: 20 20 0D 0A 42 3A 20 20 20 20 20 20 20 31 30 39 ;   ..B:       109
040h: 38 35 35 32 36 37 32 32 35 36 20 20 0D 0A 43 3A ; 8552672256  ..C:
050h: 20 20 20 20 20 20 20 34 30 38 32 34 32 30 31 32 ;        408242012
060h: 31 36 20 20 20 20 0D 0A 44 3A 20 20 20 20 20 20 ; 16    ..D:      
070h: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 ;                 
080h: 0D 0A 45 3A 20 20 20 20 20 20 20 31 30 34 32 34 ; ..E:       10424
090h: 39 38 35 36 30 30 30 30 20 20 0D 0A 46 3A 20 20 ; 98560000  ..F:  
0a0h: 20 20 20 20 20 34 30 32 32 32 39 34 31 31 38 34 ;      40222941184
0b0h: 20 20 20 20 0D 0A                               ;     ..

Но это В общем, всегда лучше избегать использования временного файла, поскольку никогда не гарантируется, что временный файл может быть вообще создан во время выполнения командного файла.

2. Системный диск не всегда C:

Windows установлен по умолчанию на диск C: и поэтому системный диск C:. Но Windows можно установить и на другой диск, в этом случае системный диск не является C:. Любой код, зависящий от данных по умолчанию вместо использования соответствующих данных, является плохо написанным кодом.

Предопределена переменная среды Windows SystemDrive с буквой диска и двоеточием диска, на котором установлен активный Windows. Переменная среды SystemRoot содержит путь к каталогу Windows, в котором находится каталог System32 со всеми исполняемыми файлами из списка Windows Commands , которые не являются внутренними командами cmd.exe.

Все эти системные переменные окружения можно увидеть по их значениям при открытии командной строки и запуске set system. Выполнение только set выводит все переменные среды с их значениями, определенными для текущей учетной записи пользователя.

3. Целочисленный диапазон значений ограничен 32-разрядным целым числом со знаком

. Командный процессор Windows cmd.exe всегда использует только 32-разрядное целое число со знаком при вычислении арифметического выражения c с set /A и для сравнения целочисленные значения с помощью команды IF при использовании операторов EQU, NEQ, LSS, LEQ, GTR, GEQ.

Поэтому диапазон целочисленных значений от −2147483648 до 2147483647. Таким образом, максимум составляет один байт менее 2 ГиБ. Значение 1099511627776 требует 64-разрядного целочисленного диапазона значений, не поддерживаемого cmd.exe.

Кстати: if [%%Y] neq [] никогда не является хорошим сравнением, поскольку [ и ] не имеют специального значения для Командный процессор Windows и neq приводят здесь сначала к подходу преобразования левой строки в 32-разрядное целочисленное значение со знаком, которое завершается ошибкой из-за того, что [ является недопустимым символом для целочисленного значения и, следовательно, запускает следующее сравнение строк при условии, что условие истинно, если сравнение строк возвращает не 0, т.е. сравниваемые строки не равны. Лучше было бы if not "%%Y" == "", который запускает прямое и более безопасное сравнение строк на неравных строках. См. Символ, эквивалентный NEQ, LSS, GTR и т. Д. c. в Windows командных файлах для получения подробных сведений о том, как команда IF выполняет сравнение строк.

Решение для получения дисков с менее чем одним свободным пространством TiB

It Рекомендуется прочитать документацию по классу, свойства которого доступны с помощью утилиты командной строки Windows Management Instrumentation. Это здесь Win32_LogicalDisk класс .

В дополнение к FreeSpace типа uint64 и DeviceID типа string вместо Caption, возможно, также полезно свойство DriveType типа uint32 для фильтрации дисков неправильного типа в дополнение к дискам со слишком большим свободным пространством и системному диску с использованием условия where при выполнении wmic.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "UsableDrive="
for /F "skip=1 tokens=1,2" %%I in ('""%SystemRoot%\System32\wbem\wmic.exe" LOGICALDISK where (DriveType=3 and FreeSpace^<1099511627776 and DeviceID!='C:') GET DeviceID,FreeSpace 2>nul"') do (
    echo Drive %%I has %%J free bytes.
    if not defined UsableDrive set "UsableDrive=%%I"
)
if defined UsableDrive echo Selected drive %UsableDrive%
endlocal

Важно знать, что FOR запускается в фоновом режиме с %ComSpec% /c еще одним командным процессом с командной строкой, указанной в ', добавленной в качестве дополнительных аргументов. По этой причине командная строка с WMI C должна удовлетворять требованиям Windows к процессору команд, описанным в справочном выводе при запуске cmd /? в окне командной строки при общем трехкратном анализе.

Первый анализ выполняется путем cmd.exe обработки пакетного файла перед выполнением команды FOR .

Второй анализ выполняется экземпляром cmd.exe при запуске его в фоновом режиме с помощью экземпляр cmd.exe, обрабатывающий пакетный файл со следующей командной строкой в ​​Windows, установленной в C:\Windows.

C:\Windows\System32\cmd.exe /c ""C:\WINDOWS\System32\wbem\wmic.exe" LOGICALDISK where (DriveType=3 and FreeSpace^<1099511627776 and DeviceID!='C:') GET DeviceID,FreeSpace 2>nul"

Третий анализ выполняется фоновым командным процессом перед выполнением wmic.exe. Оператор < в предложении where должен интерпретироваться как литеральный символ, а не как оператор перенаправления, что является причиной того, почему < экранируется с ^ для запуска wmic.exe, наконец, с:

"C:\Windows\System32\wbem\wmic.exe" LOGICALDISK where (DriveType=3 and FreeSpace<1099511627776 and DeviceID!='C:') GET DeviceID,FreeSpace

WMI C отфильтровывает с DriveType=3 все сетевые накопители, дисководы гибких дисков, дисководы CD и DVD и другие съемные накопители, RAM-диски и т. Д. c. Жесткие диски, подключенные к компьютеру через внешний USB-порт или порт eSATA, не отфильтровываются, так как для этих дисков также указано значение 3 для типа диска. Windows не может определить, установлен ли жесткий диск внутри корпуса компьютера или снаружи. Таким образом, локальный жесткий диск - это любой жесткий диск, подключенный к компьютеру, внутренним и внешним жестким дискам.

Системный диск отфильтрован со вторым условием DeviceID!='%SystemDrive%'.

Последнее условие FreeSpace<1099511627776 приводит к игнорированию всех дисков с 1 ТБ или более свободного места.

Таким образом, список уже сокращен до тех дисков, которые удовлетворяют всем трем условиям.

Для понимания используемые команды и как они работают, откройте окно командной строки , выполните там следующие команды и полностью прочитайте все страницы справки, отображаемые для каждой команды.

  • cmd /?
  • del /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • set /?
  • setlocal /?
  • type /?
  • wmic /?
  • wmic logicaldisk /?
  • wmic logicaldisk get /?

См. Также статью Microsoft о Использование операторов перенаправления команд для объяснения > и 2>nul.

0 голосов
/ 04 апреля 2020

Если я правильно понимаю ваши требования, следующая, намного более короткая, однострочная должен выводить только используемые диски, т.е. те, которые не являются системным диском, и имеют менее 1 ТБ свободного места на диске.

@(For /F "Tokens=1-2" %%G In ('""%__AppDir__%wbem\WMIC.exe" LogicalDisk Where (DeviceID !='%SystemDrive%' And FreeSpace ^<'1000000000000') Get DeviceID,FreeSpace 2>NUL|"%__AppDir%find.exe" ":""')Do @Set "_=              %%H"&SetLocal EnableDelayedExpansion&Echo %%G   !_:~-14!&EndLocal)&Pause

Поскольку вы не объяснили в своем вопросе, что вы делали с полученными данными, я просто распечатал их в окне консоли в формате, аналогичном вашему. оригинал getDiskInfo.txt, (я даже выровнял столбец байтов просто ради забавы!).

...