Posts on this page:
задача на работу с реестром.
Задача:
Решение:
Ключи и значения реестра для этой задачи можно найти тут: http://support.microsoft.com/kb/282402. В принципе, очень просто тут:
$path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' if (!(Test-Path $path)) {New-Item -ItemType Registry -Path $path -Force} if (!(Get-ItemProperty $path).MaxConnectionsPer1_0Server) { Write-Warning "MaxConnectionsPer1_0Server property doesn't exist" [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPer1_0Server' -Value 10 -PropertyType 'DWord') } else {"MaxConnectionsPer1_0Server is: " + (Get-ItemProperty $path).MaxConnectionsPer1_0Server} if (!(Get-ItemProperty $path).MaxConnectionsPerServer) { Write-Warning "MaxConnectionsPerServer property doesn't exist" [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPerServer' -Value 10 -PropertyType 'DWord') } else {"MaxConnectionsPerServer is: " + (Get-ItemProperty $path).MaxConnectionsPerServer}
и вот вывод:
[↓] [vPodans] $path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' [↓] [vPodans] if (!(Test-Path $path)) {New-Item -ItemType Registry -Path $path -Force} [↓] [vPodans] if (!(Get-ItemProperty $path).MaxConnectionsPer1_0Server) { >> Write-Warning "MaxConnectionsPer1_0Server property doesn't exist" >> [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPer1_0Server' -Value 10 -PropertyType 'DWord') >> } else {"MaxConnectionsPer1_0Server is: " + (Get-ItemProperty $path).MaxConnectionsPer1_0Server} >> if (!(Get-ItemProperty $path).MaxConnectionsPerServer) { >> Write-Warning "MaxConnectionsPerServer property doesn't exist" >> [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPerServer' -Value 10 -PropertyType 'DWord') >> } else {"MaxConnectionsPerServer is: " + (Get-ItemProperty $path).MaxConnectionsPerServer} >> WARNING: MaxConnectionsPer1_0Server property doesn't exist WARNING: MaxConnectionsPerServer property doesn't exist [↓] [vPodans] $path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' [↓] [vPodans] if (!(Test-Path $path)) {New-Item -ItemType Registry -Path $path -Force} [↓] [vPodans] if (!(Get-ItemProperty $path).MaxConnectionsPer1_0Server) { >> Write-Warning "MaxConnectionsPer1_0Server property doesn't exist" >> [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPer1_0Server' -Value 10 -PropertyType 'DWord') >> } else {"MaxConnectionsPer1_0Server is: " + (Get-ItemProperty $path).MaxConnectionsPer1_0Server} >> if (!(Get-ItemProperty $path).MaxConnectionsPerServer) { >> Write-Warning "MaxConnectionsPerServer property doesn't exist" >> [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPerServer' -Value 10 -PropertyType 'DWord') >> } else {"MaxConnectionsPerServer is: " + (Get-ItemProperty $path).MaxConnectionsPerServer} >> MaxConnectionsPer1_0Server is: 10 MaxConnectionsPerServer is: 10 [↓] [vPodans]
как бы ничего сложного.
работа с тэгами файлов. Потребуются файлы TechEd1.JPG, TechEd2.JPG и TechEd3.JPG из Competitors Pack.
Задача:
Решение:
Задача не такая и сложная, на самом деле, как кажется. Для начала нам потребуется класс System.Drawing.Bitmap и его метод GetPropertyItem. Для работы с этим классом нужно подключить библиотеку System.Drawing.dll:
[void][reflection.assembly]::loadfile("C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll")
и создать объект System.Drawing.Bitmap:
$drawing = New-Object system.drawing.bitmap -ArgumentList .\TechEd1.jpg
Этот объект будет содержать всё нам необходимое. Чтобы посмотреть нужные нам свойства, нужно получить где-то список всех ID, которым соответствуют эти свойства. К сожалению я не нашёл такого списка на MSDN, а только отдельными частями в интернете:
http://www.exif.org/specifications.html
там в конце PDF файла есть Appendix с таблицой ID на эту тему. Если посмотреть в таблицу, то увидим, что Model находится под ID = 272. Глянем, что там есть:
[↓] [vPodans] $drawing = New-Object system.drawing.bitmap -ArgumentList .\TechEd1.jpg [↓] [vPodans] $drawing.GetPropertyItem(271).value 67 97 110 111 110 0 [↓] [vPodans]
Как-то не очень радует такой вывод. Но если внимательно посмотреть на эти цифры, то можно заметить, что они не превышают число 256, что подсказывает, что это массив ASCII байтов. Эти ASCII байты можно сконвертировать в элемент вот так: [char]ASCII_Number. А т.к. это массив, то его нужно собрать либо методом ToString() или оператором –Join, который есть в PowerShell V2:
[↓] [vPodans] $drawing = New-Object system.drawing.bitmap -ArgumentList .\TechEd1.jpg [↓] [vPodans] $drawing.GetPropertyItem(271).value 67 97 110 111 110 0 [↓] [vPodans] -join ($drawing.GetPropertyItem(271).value | %{[char]$_}) Canon [↓] [vPodans]
Вот так оно всяко лучше стало. По такой же схеме выбираем и собираем все остальные свойства:
[void][reflection.assembly]::loadfile("C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll") dir *.jpg | %{ $drawing = New-Object system.drawing.bitmap -ArgumentList $_ $photo = "" | Select Name, Author, Make, Model, DateTaken $photo.Name = $_.Name $photo.Author = -join ($drawing.GetPropertyItem(315).value | %{[char]$_}) $photo.Make = -join ($drawing.GetPropertyItem(271).value | %{[char]$_}) $photo.Model = -join ($drawing.GetPropertyItem(272).value | %{[char]$_}) $photo.DateTaken = -join ($drawing.GetPropertyItem(36867).value | %{[char]$_}) $photo } | ft -AutoSize
[↓] [vPodans] [void][reflection.assembly]::loadfile("C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll") [↓] [vPodans] dir *.jpg | %{ >> $drawing = New-Object system.drawing.bitmap -ArgumentList $_ >> $photo = "" | Select Name, Author, Make, Model, DateTaken >> $photo.Name = $_.Name >> $photo.Author = -join ($drawing.GetPropertyItem(315).value | %{[char]$_}) >> $photo.Make = -join ($drawing.GetPropertyItem(271).value | %{[char]$_}) >> $photo.Model = -join ($drawing.GetPropertyItem(272).value | %{[char]$_}) >> $photo.DateTaken = -join ($drawing.GetPropertyItem(36867).value | %{[char]$_}) >> $photo >> } | ft -AutoSize >> Name Author Make Model DateTaken ---- ------ ---- ----- --------- TechEd1.JPG Ed Wilson Canon Canon PowerShot G9 2009:05:12 10:41:03 TechEd2.JPG Ed Wilson Canon Canon PowerShot G7 2009:05:12 10:25:59 TechEd3.JPG Ed Wilson Sony A-9 2009:05:12 10:26:48 [↓] [vPodans]
Вобщем, как видите, тут тоже ничего сверхсложного нету.
Работая с функциями в PowerShell можно столкнуться с одной особенностью – в качестве передачи аргументов в функции или скрипты вы можете использовать почти всё, кроме ключей (данных типа Switch). Их передавать можно, но тут есть одна особенность. Обычно это ощущается, когда вы работаете с командлетами. Возьмём простой пример:
function Test ([string[]]$Path, [String]$Filter, [switch]$Force) { Write-Host '$Path is:' $Path Write-Host '$Filter is:' $Filter Write-Host '$Force is:' $Force }
и вызовем эту функцию:
[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) { >> Write-Host '$Path is:' $Path >> Write-Host '$Filter is:' $Filter >> Write-Host '$Force is:' $Force >> } >> [↓] [vPodans] Test C:\ * -force $Path is: C:\ $Filter is: * $Force is: True [↓] [vPodans]
В принципе, всё как и ожидалось. Но если внимательно посмотреть на последний аргумент, то мы увидим лишь True, т.е. увидим факт, что ключ Force был передан. Однако, PowerShell не умеет подставять (биндить) переменную $Force (равно как и другие переменные) как именованный параметр в другую команду. Чтобы в этом убедиться, мы попробуем сымитировать нашу функцию как командлет Get-ChildItem:
[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) { >> Get-ChildItem $Path $Filter $Force >> } >> [↓] [vPodans] Test C:\ * -Force Get-ChildItem : A positional parameter cannot be found that accepts argument 'True'. At line:2 char:14 + Get-ChildItem <<<< $Path $Filter $Force + CategoryInfo : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand [↓] [vPodans]
Как видно, командлет Get-ChildItem не смог сопоставить последний аргумент ни с одним из своих параметров. Т.е. по факту выполнилась следующая строка:
Get-ChildItem -Path C:\ -Filter * True
PowerShell не смог сказать командлету, что мы указали ключ Force и хотим именно его передать в командлет. По факту в этом коде Get-ChildItem не знал, какие параметры ему были переданы и подставлял их на основе номера позиции параметра. А т.к. у Get-ChildItem нету параметра с порядковым номером 3 (с номером 1 идёт Path или LiteralPath, а с номером 2 идёт Filter. Остальные параметры именованные), то мы получили ошибку. Но всё же, как выкручиваться из этой ситуации? Вы можете как угодно пытаться подставить аргументы, но ничего не выйдет. Для этих целей в PowerShell V2 появилась специальная переменная - $PSBoundParameters. Эта переменная по сути представляет собой хэш-таблицу:
[↓] [vPodans] function Test ([string]$Path, [String]$Filter, [switch]$Force) { >> $PSBoundParameters >> } >> [↓] [vPodans] Test C:\ * -Force Key Value --- ----- Force True Path C:\ Filter * [↓] [vPodans]
В отличии от первого примера переменная $PSBoundParameters содержит не только значения переменных, но и их имена (в перовм примере я вручную дописывал имена переменных), которые используются в качестве именованных параметров. Т.е. при подстановке аргументов в команду, она сначала выбирает имя переменной в качестве именованного параметра и значение переменной подставляет в качестве аргумента этого параметра. Синтаксис использования этой переменной очень прост:
function Name ($arg1, $arg2, $arg3 ... $argN) {
Command @PSBoundParameters
}
и в результате будет исполняться вот такая команда:
Command –arg1 <значение $arg1> –arg2 <значение $arg2> –arg3 <значение $arg3> … –argN <значение $argN>
Т.е. будут подставляться именованные параметры и значения переменных соответствующих аргументов. Давайте проверим, как это подействует на наш пример с Get-ChildItem:
function Test ([string]$Path, [String]$Filter, [switch]$Force) { Get-ChildItem $PSBoundParameters }
[↓] [vPodans] function Test ([string]$Path, [String]$Filter, [switch]$Force) { >> Get-ChildItem @PSBoundParameters >> } >> [↓] [vPodans] Test C:\ * -Force Directory: C:\ Mode LastWriteTime Length Name ---- ------------- ------ ---- d--hs 10.07.2007. 19:44 $Recycle.Bin d--hs 09.03.2008. 17:53 Boot d---- 23.11.2008. 19:41 inetpub d-rh- 26.06.2007. 22:27 MSOCache d---- 09.03.2008. 17:41 PerfLogs <...>
И вы можете видеть, что у нас всё получилось в наилучшем виде! В сравнении с предыдущим примером фактически выполнилась следующая команда:
Get-ChildItem -Path C:\ -Filter * -Force
Для формирования аргументов для командлетов в функциях (так называемые wrapped cmdlets) это самый красивый и идеальный вариант. Если вы захотите изменить логику стандартных командлетов или добавить в них свой функционал, то $PSBoundParamters сделает за вас очень много лишней работы. Но это не единственное полезное применение для этой переменной. Она так же позволяет сократить возможность ошибки при вызове функции внутри скрипта или другой функции, которая принимает те же аргументы. Давайте посмотрим ещё один пример:
function Test ([string[]]$Path, [String]$Filter, [switch]$Force) { Write-Host '$Path in Test is:' $Path Write-Host '$Filter in Test is:' $Filter Write-Host '$Force in Test is:' $Force Write-Host -------------------------------- function Test2 ([switch]$Force, [string[]]$Path, [String]$Filter) {
Write-Host '$Path in Test2 is:' $Path Write-Host '$Filter in Test2 is:' $Filter Write-Host '$Force in Test2 is:' $Force } Test2 @PSBoundParameters }
Что мы делаем: мы создали функцию Test, которая принимает набор аргументов. Внутри этой функции есть другая функция Test2, которая принимает те же аргументы. Далее из функции Test вызываем функцию Test2 и с помощью $PSBoundParameters передаём в неё аргументы. Обратите внимание, что в функции Test2 я изменил порядок аргументов. Это сделано для того, чтобы показать, что $PSBoundParamters отсортирует наши аргументы. А теперь внимание на экран:
[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) { >> Write-Host '$Path in Test is:' $Path >> Write-Host '$Filter in Test is:' $Filter >> Write-Host '$Force in Test is:' $Force >> Write-Host -------------------------------- >> function Test2 ([switch]$Force, [string[]]$Path, [String]$Filter) { >> Write-Host '$Path in Test2 is:' $Path >> Write-Host '$Filter in Test2 is:' $Filter >> Write-Host '$Force in Test2 is:' $Force >> } >> Test2 @PSBoundParameters >> } >> [↓] [vPodans] Test C:\ * -Force $Path in Test is: C:\ $Filter in Test is: * $Force in Test is: True -------------------------------- $Path in Test2 is: C:\ $Filter in Test2 is: * $Force in Test2 is: True [↓] [vPodans]
И смотрите, что у нас получилось. А у нас получилось, что функция Test2 получила тот же набор аргументов, причём они были подставлены в правильном соответствии. Фактически строка Test2 @PSBoundParameters была преобразована в:
Test2 -Path $Path -Filter $Filter -Force $true
Т.е. в вариант, который мы вынуждены использовать в PowerShell 1.0. Плюс, мы имеем возможность подставлять позиционно ключи (объекты типа Switch), что есть очень позитивно.
Навеяно множеством мотивов:
Как мне кажется, с Write-Progress вряд ли получится что-то хорошее, поэтому немного сориентировался и нашёл вот это: PowerShell Script: Copy-FilePlus. Именно этот скрипт взят за основу визуального окна. А за основу логики был взят мой простенький скрипт копирования файлов с сохранением структуры каталогов:Лёгкая разминка. Приведённый по ссылке на Copy-FilePlus вариант Хала Роттенберга является достаточно базовым, поскольку не позволяет копировать за раз множество файлов, плюс требуется вводить имя конечного файла. Я решил его немного расширить и добавить следующий функционал:
заранее оговорюсь, что скрипт обладает одним недостатком: не будет единого прогресс-бара для всех файлов, а только для каждого файла свой (издержки .NET). Зато хоть что-то и весьма актуально при копировании больших файлов. Код получился несколько солидным, но я его постарался снабдить комментариями о коде и о логике, на которой он работает. Причём следуя примеру Роттенберга я скрипт тоже оформил в Advanced Function. По advanced functions я в скрипте вложил ссылки, как почитать о них во встроенной справке PowerShell. И, собственно, сам код:
######################################################## # Copy files with GUI.ps1 # Version 1.3 # # Copies single or couple files with GUI progressbar # # Original idea: Oisín Grehan # First edition: Hal Rottenberg # Second edition: Vadims Podans # # Vadims Podans (c) 2009 # http://www.sysadmins.lv/ ######################################################## # сразу после названия идёт описание к функции. После загрузки функции в консоль # её справка будет доступна в консоли. Достаточно будет набрать: # Get-Help Copy-FilesPlus # вобщем, как в настоящих командлетах function Copy-FilesPlus { <# .Synopsis Copies files and folders displaying GUI progress bar. .Description This is a script, that demonstrates how PowerShell can use useful .NET types and PowerShell V2 capabilities. .Parameter Path Specifies the filename or FileInfo object representing file to be copied. Objects can be passed through a pipeline. .Parameter Destination Specifies the path for resulting copy operation .Parameter Recurse Gets the items in the specified locations and in all child items of the locations. Used only when source directory passed through argument list. .Parameter Force Creates directory structure in destination folder and copies files to their source respective folders (Tree copy). .EXAMPLE PS > Copy-FilesPlus -Path C:\tmp -Destination e:\Users This will copy only files from C:\tmp to E:\Users .EXAMPLE PS > Get-Item C:\tmp\windows7.iso | Copy-FilesPlus -Destination E:\Users This will copy specified file from C:\tmp folder to e:\Users .EXAMPLE PS > Get-Childitem D:\Shared | Copy-FilesPlus -Destination E:\ This will copy all files from Shared folders to E: drive root directory .EXAMPLE PS > Get-Childitem D:\Shared -Recurse | Copy-FilesPlus -Destination E:\ -Force This will copy all files in Shared folder and subfolders. Shared folder will be a tree root point. All directory structure will be copied with files to destination folder. .EXAMPLE PS > Copy-FilesPlus C:\Users\User E:\ -Recurse This will copy all files from User folder and subfolders to destination directory without copying source folders tree .EXAMPLE PS > Copy-FilesPlus C:\Users\User E:\ -Recurse -Force This will copy all files in User folder and subfolders. User folder will be a tree root point. All directory structure will be copied with files to destination folder. .ReturnValue Genrally, script don't return anything, except errors! .Link about_functions about_functions_advanced about_functions_advanced_methods about_functions_advanced_parameters #Requires -Version 2.0 #> # ну и теперь фишки от advanced functions в V2. CmdletBinding делает # подстановку передаваемых аргументов в функцию. Если аргумент не передан # то PowerShell попросит его ввести, а не вывалится с ошибкой [CmdletBinding()] param ( # первый аргумент. Он является обязательным и он может принимать значения # из конвейера. Причём, внутри блока Process {} для обозначения текущего элемента # можно использовать, как переменную $path, так и $_. [Parameter(Mandatory = $true,ValueFromPipeline = $true)] $Path, [Parameter(Mandatory = $true)] [string]$Destination, [switch]$Recurse, [switch]$Force ) begin { # пробуем создать папку назначения, куда будут копироваться файлы. [void](md $Destination -Force -ea 0) # вот тут я сделал переменную для счётчика. Счётчик мне потребуется для того, # чтобы при использовании ключа -Force и если файлы передаются по конвейеру # можно было брать точку начала дерева структуры, которая будет копироваться. $n = 0 # временная функция, которая выполняет само копирование. Тут нужно учесть то, # что путь назначения должен указываться в полном формате с указанием имени # конечного файла. Относительные пути тут не поддерживаются. Поэтому дальше # в коде я буду сохранять имя оригинального файла. т.е. переименовывание файлов # на лету не поддерживается function _routinecopy_ ([string]$Destination) { process { Add-Type -AssemblyName microsoft.visualbasic [Microsoft.VisualBasic.FileIO.FileSystem]::CopyFile($_, $Destination, [Microsoft.VisualBasic.FileIO.UIOption]::AllDialogs, [Microsoft.VisualBasic.FileIO.UICancelOption]::ThrowException) } } } process { try { # вот здесь я проверяю, откуда пришли файлы - через аргументы или через конвейер # если данные пришли из обоих путей, то приоритет за конвейером if ($_) { # проверяем, что объект существует и что это объект файловой системы $File = gi $Path.FullName -ea stop | ?{$_.PsProvider -match "FileSystem$"} if ($File) { # если объект в порядке и выполняется только первая итерация конвейера # то мы задаём точку начала дерева. Весь путь от этой точки до имени файла # будет копироваться в папку назначения if ($n -eq 0) { # здесь отрезаем от файла структуру папок, которая будет являться границей дерева $RootPoint = $Path.FullName -replace $([regex]::Escape($Path.Name)) # заодно на основе этой структуры делаем регулярное выражение. Этим регулярным # выражением будем у всех последующих файлов отрезать начало и оставлять необходимую # часть дерева $RootRegEx = [regex]::Escape($RootPoint.Substring(0,$RootPoint.Length -1)) # важно, что эту операцию нужно проделать единожды, чтобы точка монтирования дерева # больше не менялась в процессе. Поэтому увеличиваем счётчик и тогда в течении текущего # процесса копирования код сюда не вернётся $n++ } # проверяем, что нужно ли копировать дерево или нет. if ($Force) { # если копируем дерево, то выбрасываем папки и работаем только с файлами $File = $File | ?{!$_.PsIsContainer} if ($File) { # если есть файлы для копирования, то выбираем весь путь папок до текущего файла # и заранее приготовленным регэкспом отрезаем начало. В переменную $rep мы запишем # дерево папок от точки монтирования дерева до имени файла $rep = $Path.Directory.ToString() -replace $RootRegEx # а теперь к папке назначения пристыковываем дерево папок от точки монтирования до имени файла $DestFolder = Join-Path $Destination $rep # заранее создаём начальный хвостик папок в целевой папке и подавляем вывод на экран [void](md $DestFolder -Force -ea 0) # а теперь к новому конечному пути пристыковываем имя файла $Dest = Join-Path $DestFolder $Path.Name # и теперь подаём текущий файл в функцию копирования. Вот тут мы и увидим прогресс-бар. $Path.FullName | _routinecopy_ $Dest } } else { # если структуру папок копировать не надо, то все файлы, что пришли с конвейера будут копироваться # в папку назначения без создания структуры папок. if (!$_.PsIsContainer) { $Dest = Join-Path $Destination $File.Name $File | _routinecopy_ $Dest } } # если объект не существует или это не объект файловой системы (защита от дураков, да-да :)), то ругаемся } else {throw "Input object does not represent any applicable FileSystem object"} } else { # если данные об источнике копирования переданы через аргументы, то проверяем, что заданный путь допустим # и объект, который мы получим после Get-Item является объектом файловой системы $File = gi $Path -ea stop | ?{$_.PsProvider -match "FileSystem$"} if ($File) { # если всё хорошо, то указанный файл или папка становятся точкой монтирования дерева $RootPoint = Resolve-Path $Path # если ключи -Force -Recurse не указаны, то копируется либо указанный файл или все файлы в указанной # папке в папку назначения if (!$Recurse) { dir $RootPoint | ?{!$_.PsIsContainer} | %{$Dest = Join-Path $Destination $_.Name $_.FullName | _routinecopy_ $Dest} # хитрый режим, когда рекурсивно выбираются все файлы в указанной папке и всех подпапках и копируются # в папку назначения без сохранения структуры (т.е. в папке назначения будет большая куча файлов). } elseif ($Recurse -and !$Force) { dir $RootPoint -Recurse | ?{!$_.PsIsContainer} | %{$Dest = Join-Path $Destination $_.Name $_.FullName | _routinecopy_ $Dest} # если указаны оба ключа, то мы повторяем структуру исходных папок относительно точки монтирования дерева # в папке назначения } elseif ($Recurse -and $Force) { dir $RootPoint -Recurse | ?{!$_.PsIsContainer} | %{ # тут мы делаем то же самое, что и в случае, когда файлы пришли с конвейера $RootRegEx = [regex]::Escape($RootPoint) $rep = $_.Directory.ToString() -replace $RootRegEx $DestFolder = Join-Path $Destination $rep [void](md $DestFolder -Force -ea 0) $Dest = Join-Path $DestFolder $_.Name $_.FullName | _routinecopy_ $Dest } } } else {throw "Input object does not represent any applicable FileSystem object"} } } catch {$_} } }Вот такой скриптик у меня получился. Местами даже очень умный, почти как я :-). Как мне кажется, на этом коде тоже можно чему-то поучиться. Если будут вопросы – то welcome в комментарии. :-)
Тут обнаружился один интересный командлет – ConvertFrom-StringData, который позволяет преобразовывать строку в хэш-таблицы. Скажем, есть файл вида:
ключ1 = значение1
ключ2 = значение2
ключ3 = значение3
ключ4 = значение4
при этом иногда очень хочется работать с этими строками как с объектами. Т.е. при указании объекта и его ключа, например, $a.key1 получить его значение. Как это делается в хэш-таблицах. Вот пример:
[vPodans] $a = @{"key1"="value1";"key2"="value2"}
[vPodans] $a
Name Value
---- -----
key2 value2
key1 value1
[vPodans] $a.key1
value1
[vPodans] $a.key2
value2
[vPodans]
здесь я создал простую хэш-таблицу. Но если у нас есть файл общего вида, ключ = значение, то его можно легко привести в вид хэш-таблиц:
[vPodans] $a = "key1 = value1"
[vPodans] $a
key1 = value1
[vPodans] $a.GetType().FullName
System.String
[vPodans] $b = ConvertFrom-StringData -StringData $a
[vPodans] $b
Name Value
---- -----
key1 value1
[vPodans] $b.GetType().FullName
System.Collections.Hashtable
[vPodans] $b.key1
value1
[vPodans]
сперва я создал строку, которая состоит из пары ключ = значение, в чём мы можем убедиться в типе данных. Вторым этапом я сконвертировал эту строку в хэш-таблицу. Однако, следует учесть, что такое возможно только для строки, но не массива строк. Вот так легко попасть в засаду:
[vPodans] $a = gc keys.txt
[vPodans] $a
key1 = value1
key2 = value2
key3 = value3
key4 = value4
[vPodans] $a[0]
key1 = value1
[vPodans] ConvertFrom-StringData -StringData $a
ConvertFrom-StringData : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'StringData
'. Specified method is not supported.
At line:1 char:35
+ ConvertFrom-StringData -StringData <<<< $a
+ CategoryInfo : InvalidArgument: (:) [ConvertFrom-StringData], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.ConvertFromStringDataCommand
[vPodans]
командлет Get-Content (или его алиас GC, не путать с глобальным каталогом) читает файл построчно в виде массива. Следовательно каждая строка является отдельным элементом массива. Как быть в такой ситуации? На первый взгляд может показаться, что можно Get-Content разобрать через Foreach-Object и уже отдельные элементы массива подавать конвертеру в качестве строк. Смотрим:
[vPodans] $a = gc keys.txt | %{ConvertFrom-StringData -StringData $_}
[vPodans] $a
Name Value
---- -----
key1 value1
key2 value2
key3 value3
key4 value4
[vPodans] $a.key1
[vPodans] $a.GetType().FullName
System.Object[]
[vPodans] $a[0].key1
value1
[vPodans]
Как видите, мы на выходе получили не одну хэш-таблицу, а массив одиночных хэш-таблиц и для доступа к его ключам и значениям нужно ещё указывать номер элемента в массиве. Чтобы решить данную проблему нужно каким-то образом прочитать файл не построчно, а в виде целой строки. Для чтения файла целиком можно воспользоваться методом ReadAllText класса File Class. Как видно из описания метода, он читает текст в единую строку. Давайте посмотрим, что у нас получится:
[vPodans] $a = [io.file]::ReadAllText("keys.txt")
[vPodans] $a
key1 = value1
key2 = value2
key3 = value3
key4 = value4
[vPodans] $a.GetType().FullName
System.String
[vPodans] $b = ConvertFrom-StringData -StringData $a
[vPodans] $b
Name Value
---- -----
key2 value2
key4 value4
key1 value1
key3 value3
[vPodans] $b.key1
value1
[vPodans] $b.key2
value2
[vPodans] $b.key3
value3
[vPodans] $b.key4
value4
[vPodans]
или просто в одну строчку:
ConvertFrom-StringData -StringData ([io.file]::ReadAllText("keys.txt"))
Вот так просто в стиле The PowerShell Way (в одну строчку) можно текстовые файлы сконвертировать в хэш-таблицы и работать с ними как с объектами, что есть удобно и полезно.
Продолжая цикл постов о новых командлетах в PowerShell V2 CTP3 хочу рассказать про несколько командлетов для управлением компьютера как клиента в домене Active Directory или рабочей группе. Отмечу сразу, что эти командлеты не управляют доменом Active Directory, а только компьютером-клиентом домена (не знаю, как по-русски правильно сформулировать мысль). По сути эти командлеты повторяют аналог утилиты netdom.exe и вот их список:
и ещё 2 на закуску:
Ну и как обычно - рассмотрим каждый из них:
1) Add-Computer - добавляет компьютер к домену Active Directory или перемещает компьютер между рабочими группами (Workgroup). Данный командлет содержит следующие параметры и ключи:
Примечание: может показаться, что ключ -Reboot очень полезный, но мне так не кажется. И вот почему. Если командлет сам не перезагружает машину после завершения операции, то это нужно сделать вручную. Но перезагружать машину можно только в случае если всё прошло успешно. Но если по каким-то причинам ожидаемый результат не был достигнут, то компьютер будет перезагружен в пустую. Я на этом не настаиваю, но мне это видится именно так.
Hint: не забывайте, что если указываются позиционные параметры, то их имена указывать не обязательно. Например, ComputerName позиционно является первым параметром и если указывать за командлетом сразу имена компьютеров, то название параметра -ComputerName можно и не указывать.
По умолчанию данный командлет не выводит результат на экран, поэтому для вывода результата исполнения можно использовать ключи -PassThru или -Verbose. Так же командлет имеет такие полезные ключи как -Confirm для ручного подтверждения выполнения операции и -WhatIf для моделирования работы командлета. И несколько примеров использования командлета:
Add-Computer -DomainName contoso.com -Credential (Get-Credential) -OUPath OU=Test,OU="3th floor",DC=Contoso,DC=com -Reboot
Add-Computer -ComputerName (Get-Content Comps.txt) -WorkgroupName MSHome -PassThru
2) Remove-Computer - удаляет компьютер из домена или рабочей группы. Содержит параметры -ComputerName, -Credential (учётные данные пользователя, который имеет право на вывод машины из домена или локального администратора, если компьютер перемещается в рабочих группах), -Reboot, -Confirm, -PassThru и -WhatIf. Правила использования этих параметров и ключей такие же, что и для Add-Computer, но только в контексте удаления, а не ввода машины во что-то. Пример:
Remove-Computer -ComputerName computer1, computer2 -Credential contoso.com\Administrator -PassThru -Reboot - выводит компьютер из домена. Следует учесть, что при выполнении команды будет запрошен пароль учётной записи, указанной в Credential.
Remove-Computer MyComputer - просто удаляет компьютер из рабочей группы (интересно, куда? :-D )
Примечание: ни в коем случае не удаляйте так контроллеры домена. Их сначала нужно понизить до роли рядового сервера командой dcpromo и только потом с помощью UI, netdom или командлета Remove-Computer выводить из домена.
3) Rename-Computer - переименовывает компьютер или компьютеры в рабочей группе или домене Active Directory. Обычно использует следующие параметры:
так же командлет содержит такие ключи как -Confirm, -Reboot и -WhatIf.
Примечание: ни в коем случае не пытайтесь этим командлетом переименовать контроллер домена!
4) Reset-ComputerMachinePassword - сбрасывает пароль учётной записи компьютера в базе Active Directory. Используется только для доменных компьютеров. Данный командлет можно использовать при трудностях аутентификации компьютера в домене или при устаревании пароля. Несколько типичных случаев, когда требуется сброс пароля компьютера - компьютер не аутентифицировался в домене более 30 или 60 дней, в зависимости от настроек домена; компьютер был восстановлен из бэкапа (SystemState), срок которого старше 30 или 60 дней или компьютер был восстановлен из образа без дополнительного восстановления актуального SystemState и другие случаи.. Подробности этой темы выходят за рамки этого поста.
Из актуальных параметров содержит -ComputerName (можно указывать несколько компьютеров. Допускаются FQDN, NetBIOS имена или IP адреса), -Server - имя контроллера домена, который будет производить сброс пароля (не обязательный параметр) и -Credental - имя пользователя, который имеет права сброса паролей указанных компьютеров. Из ключей можно выделить такие как -Confirm и -WhatIf. Общий синтаксис такой:
Reset-ComputerMachinePassword (get-content comp.txt) - переустановит пароль всех компьютеров из списка comp.txt
Reset-ComputerMachinePassword - переустановит пароль текущего компьютера
Reset-ComputerMachinePassword -Server dc1.contoso.com -Credential (Get-Credential) -Confirm - переустановит пароль локального компьютера на контроллере домена с именем DC1 и с вводом альтернативных учётных записей. После запуска потребует подтверждения операции.
5) Test-ComputerSecureChannel - проверяет возможность установки безопасного канала между клиентом и сервером. Работает только в домене и возвращает True или False. Имеет один параметр и ключ:
И содержит дополнительные ключи как -Confirm и -WhatIf.
Я не придумал к какой категории присвоить эти 2 командлета и решил их описать здесь. Кстати, очень удобные командлеты:
6) Restart-Computer и Stop-Computer. Первый командлет перезагружает локальный или удалённый компьютер (или несколько), а второй выключает их совсем. Это очень полезно, поскольку я видел как минимум 5 различных решений перезагрузки/выключения компьютеров в скриптах PowerShell. Как правило это либо WMI, либо использование штатного shutdown.exe (кстати говоря, я им пользуюсь всегда) либо ещё что-нибудь. Теперь можно будет этот момент стандартизировать. Много говорить про них не буду, а скажу только, что эти командлеты используют метод Win32Shutdown WMI класса Win32_OperatingSystem. Следовательно эти командлеты обладают всеми новыми возможностями, которые описаны в предыдущей статье: Обзор новых командлетов PowerShell V2 CTP3 - WMI. Одно из преимуществ - можно использовать фоновую работу. Скажем, отправить в ребут сотню компьютеров и пока это всё происходит спокойно работать в консоли (шутка :-) ). Поэтому в этих командлетах можно использовать такие параметры и ключи как -AsJob, Impersonate, Authentication, ComputerName и другие. Из уникальных отмечу один параметр:
и ключ:
На сегодня вроде всё. Вроде ничего не пропустил.