Тема: CMD/BAT: оперируем USB-flash накопителями.
Доброго времени суток.
Пролог.
У меня возникла такая ситуация: пару раз в неделю ко мне приходят 2-6 человек которым надо записать на flash-накопитель определённый набор файлов (каждому человеку свой). Я беру накопитель, подключаю к ПК, ухожу домой, в 3:00 планировщик стартует скрипт, утром, прийдя на работу, я отдаю "флешки" обратно.
Особенности таковы - накопитель может имет повреждённую ФС (что сразу отсекает возможность хранения списка файлов на самой "флешке"), заражённый разными "венерическими" заболеваниями и т.д. и т.п.
Алгоритм таков:
Получаем список накопителей, подключенных к рабочей станции.
Точно идентифицируем накопитель.
Форматируем.
Копируем файлы согласно списку.
Сохраняем логи на накопитель.
Извлекаем накопитель.
В случаях получения списка "флешек" и идентификации используется PNPDeviceId, но тут есть подводные камни. Так, к примеру, выглядит список из 2-х Kingston DTR-30/64 (USB-3.0, 64Gb):
USBSTOR\DISK&VEN_KINGSTON&PROD_DT_RUBBER_3.0&REV_1.01\0011A11C0FF5ABC1234567Z0&0
USBSTOR\DISK&VEN_KINGSTON&PROD_DT_RUBBER_3.0&REV_1.01\0011A11C0FF5ABC1234589Z0&0
PNPDeviceId постоянен, не зависит от порта, хаба и, скорее всего, на др. ПК точно такой же.
А так Transcend "чего-там" (USB-2.0, 64Gb):
USBSTOR\DISK&VEN_JETFLASH&PROD_TRANSCEND_64GB&REV_8.07\7&2A17C003&1&A0ABCDEF&0
Она же, подключенная к другому USB-порту в USB-hub'е:
USBSTOR\DISK&VEN_JETFLASH&PROD_TRANSCEND_64GB&REV_8.07\7&E2E6521&0&A0ABCDEF&0
Два остальных, "тестовых" Transcend JetFlash (USB-2.0, 16 и 4 Gb) "ведут себя" точно так же.
Ясно, только то, что опираться придётся на "неизменяемую часть" PNPDeviceId (для всех трёх "флешек"):
0011A11C0FF5ABC1234567Z0
0011A11C0FF5ABC1234589Z0
A0ABCDEF
1. Получаем список накопителей (get-usblist.cmd)
@echo off
::"Рабочим" кодом в данном случае являются 3 WMI запроса
setlocal enabledelayedexpansion
set n=0
:: 1-й WMI-запрос - получаем PNPDeviceId всех дисков с интерфейсом USB
for /f "usebackq skip=1 tokens=1,2" %%a in (`wmic PATH "win32_DiskDrive.InterfaceType='USB'" get DeviceID ^, PNPDEviceID`) do (if not "%%b" == "" call :enumerate "%%a" "%%b")
exit /b 0 & endlocal
:enumerate
set "usbflash.%n%.devid=%~2"
set "usbflash.%n%.diskid=%~1"
call :getpart "!usbflash.%n%.diskid!"
:: "win32_DiskDriveToDiskPartition" и "win32_LogicalDiskToPartition" имеют различный формат
:: записи имени дисковых разделов.
:: Утверждение справедливо для WMIC - для WSH [JScript/VBScript] и Powershell формат одинаков.
:: Преобразование имён дисковых разделов:
set usbflash.%n%.diskpart=!usbflash.%n%.diskpart:#= #!
set usbflash.%n%.diskpart=!usbflash.%n%.diskpart: = !
call :getletter !usbflash.%n%.diskpart!
echo.!usbflash.%n%.devid!;!usbflash.%n%.diskletter!
set /a n=!n!+1
exit /b 0
:getpart
:: 2-й WMI-запрос - получаем разделы (partition) для USB-устройств
for /f "usebackq tokens=2*" %%c in (`wmic PATH win32_DiskDriveToDiskPartition^|find "%~1"`) do (
for /f "tokens=2 delims==" %%e in ('echo.%%c%%d') do (set usbflash.%n%.diskpart=%%e))
exit /b 0
:getletter
:: 3-й WMI-запрос - соотносим разделы (partition) с буквами логических дисков
for /f "usebackq tokens=5" %%c in (`wmic PATH win32_LogicalDiskToPartition^|find %1`) do (
for /f "tokens=2 delims==" %%e in ('echo.%%c') do (set usbflash.%n%.diskletter=%%~e\))
exit /b 0
Получилось очень похоже на код, смотрите Get-UsbList { ... }, хотя я использовал другие источники (scriptomatic и ветку со stackowerflow) - видимо другие способы требуют более извращенного ума.
Скрипт "спотыкнётся" на устройствах с несколькими разделами. Точнее - "увидит" только первый.
Я не тестировал на разных версиях ОС, писалось для Windows 7 Pro Sp1 or w/o Sp1. Вполне допускаю, что для Windows XP Pro Sp3 всё работает, но опять же - не проверял.
Требует наличия прав Администратора, можно ограничиться локальным администратором или запускать от имени System.
2. Идентифицируем накопитель.
Подготавливаем список PNPDeviceId вида:
0011A11C0FF5ABC1234567Z0
0011A11C0FF5ABC1234589Z0
A0ABCDEF
identify.cmd
@echo off
:: передаём список с PNPDeviceId в %1
for /f "tokens=*" %%e in ('type "%~1"') do (call :identify %%e)
exit /b 0
:identify
:: Поле для оптимизаций
:: - лучше сохранять вывод get-usblist.cmd в файл
:: т.к. запуск и выполнение wmic, довольно долгий процесс.
:: - можно заменить FIND на FINDSTR и
:: "поиграть" с регулярными выражениями.
for /f "delims=; tokens=1,2" %%a in ('call get-usblist.cmd ^| find /i "%~1"') do (
call :identified "%~1" "%%b")
:identified
:: Проверка введена, т.к. при отладке было выявлено "странное" поведение скрипта -
:: он выводил вконсоль записи вида:
::
:: PNPDeviceId;Letter:\
:: PNPDeviceId;
::
:: Возможно, требуется переработка get-usblist.cmd, т.к. wmic "любит" выдавать
:: в консоль "лишние" символы (пробелы, табуляцию).
if not "%~2" == "" (echo.%~1;%~2)
exit /b 0
Возможно неправильное определение накопителя, но все претензии к производителям
.
3. Форматируем.
Тут уже вариантов и "граблей" море.
Собственно мои особенности:
Форматирую в NTFS (не кидайтесь помидорами далее по тексту объясню зачем).
Создаю одну директорию в корне (например G:\ROOT).
Выставляю права на корень "Все, только чтение, владелец - Администратор"
Выставляю права на "G:\ROOT" "Все, полный доступ, владелец - Администратор"
format.cmd
@echo off
:: стоит добавить нормальный разбор ком.строки и контроль ошибок выполнения
:: для WinXP "не канает"
call :fmt_%~1 %2 %3
exit /b
:fmt_quick
echo y| format.com %1: /X /Q /V:%2 /FS:NTFS
exit /b 0
:fmt_full
echo y| format.com %1: /X /V:%2 /FS:NTFS
exit /b 0
:fmt_convert
convert.exe %1: /FS:NTFS /X /NoSecurity
exit /b 0
С созданием директории проблем никаких:
md %1:\ROOT
Выставляем права, protect.cmd:
:: Используются TAKEOWN и XCACLS из состава Windows Server 2003
:: В теории можно обойтись (CACLS, SUBINACL и др.) - меня устроило и так.
:: Владелец корня - Администратор
TAKEOWN /F %1:\ /R /A /D Y
:: Выставляем права на "корень" - только чтение для "Всех"
XCACLS %1:\ /C /P Все:R /Y
:: получаем список папок в "корне" даём для каждой папки полные права для "всех"
for /f "tokens=1 delims=#" %%a in ('dir /ad /b /on %1:\') do (call XCACLS "%1:\%%a" /T /C /P Все:F /Y)
exit /b 0
Сохраняйте в DOS-кодировке (cp866), иначе можете выставить права "чёртзнаеткому"!!!
"Кому это было нужно?"
Да, многие, думаю, уже поняли, что в NTFS форматируются "флешки" ради "прав доступа на ФС". Смысл в том, что, несмотря на не самый лучший режим работы накопителя, их носят люди в разные места - госконторы, коммерческие организации и даже, возможно, к вам домой. Как конечный клиент отреагирует на визг %ваш_любимый_антивирус%, и вообще всё взорвалось!? Таким "детским и нехитрым" способом отсекается создание всяких AUTORUN.INF, RECYCLE и создание чего-либо в корне. Такой вот суровый корпоративный антивирус
. Честно не видел malware, которое умеет и понимает NTFS ACL, но это не означает того, что его нет.
В данном пункте тоже возможны варианты:
Для форматирования можно воспользоваться DISKPART, утилитами от HP. USB-flash "ёмкостью" 128Gb - "не за горами".
Права доступа редактируем чем-либо из CACLS, SUBINACL, NTRIGHTS, SETACL
4. Копируем файлы согласно списку.
5. Сохраняем логи на накопитель.
Тут ничего сложного нет - robocopy с логированием и, по окончанию копирования, "кладём" журналы на накопитель.
@echo off
setlocal enabledelayedexpansion
:: Добавляем тут свои переменные
set letter=%1
set letter=!letter::\=!
if exist %cd%\%letter% (
echo DISK %LETTER% IS BISY!!!
pause
endlocal & exit /b 0
) else (
echo."copying %1">%letter%
:: ...
for /f "delims=; tokens=1,2" %%i in ('type copy.lst') do (
md %letter%:\ROOT\%%j
robocopy %sourcedir%\%%i\%%j %letter%:\ROOT\%%j /mir /np /TEE /LOG:LOG\%%j.LOG
)
:: ...
del /f/q %letter%
endlocal & exit 0
)
6. Безопасно извлекаем накопитель.
Возможно несколько вариантов, я остановился на способе отсюда.
eject.cmd
powershell.exe %~dp0eject.ps1 -Letter %1:
Также, "одна бабка сказала", что на модемах от HUAWEY (USB 3G) есть "бинарник" который извлекает накопитель, с которого был запущен.
Более того, думаю, что кто-нибудь написал кучу утилит для извлечения USB-flash, и даже продаёт их - мне подошёл вариант, описанный мной первым.
Эпилог.
Долго думал надо это кому-либо или нет. Пусть будет, может быть, кому-то пригодится. Скорее всего последей попыткой переработать всё это, будет переписывание на PoSh или WSH/JScript.
Идеи/предложения/плевки/камни/помидоры в студию! ![]()
PS: очепяток и ошибок, по-видимому, море, по нахождению - исправляю.
Нет проверки на наличие необходимого места.
Периодически возникают "глюки" с копированием файлов - на конечный накопитель записываются файлы забитые нулями (справедливо для источника расположенного на NAS/сервере).
Неплохо бы создавать/сравнивать CRC32/MD5 для копируемых эл-тов - работы в этом направлении вёл (на основе fciv), но потом забросил.
Также вышеописанное (п.1 и п.2) можно использовать как основу для простенькой базы инвентаризации накопителей.

