PowerShell: часто задаваемые вопросы. Продолжение::Журнал СА 4.2008
www.samag.ru
Льготная подписка для студентов      
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
О журнале
Журнал «БИТ»
Подписка
Где купить
Авторам
Рекламодателям
Магазин
Архив номеров
Вакансии
Контакты
   

Jobsora


  Опросы

Какие курсы вы бы выбрали для себя?  

Очные
Онлайновые
Платные
Бесплатные
Я и так все знаю

 Читать далее...

1001 и 1 книга  
28.05.2019г.
Просмотров: 1943
Комментарии: 2
Анализ вредоносных программ

 Читать далее...

28.05.2019г.
Просмотров: 1973
Комментарии: 1
Микросервисы и контейнеры Docker

 Читать далее...

28.05.2019г.
Просмотров: 1530
Комментарии: 0
Django 2 в примерах

 Читать далее...

28.05.2019г.
Просмотров: 1117
Комментарии: 0
Введение в анализ алгоритмов

 Читать далее...

27.03.2019г.
Просмотров: 1693
Комментарии: 1
Arduino Uno и Raspberry Pi 3: от схемотехники к интернету вещей

 Читать далее...

Друзья сайта  

Форум системных администраторов  

sysadmins.ru

Электронка - 2020!

 PowerShell: часто задаваемые вопросы. Продолжение

Архив номеров / 2008 / Выпуск №4 (65) / PowerShell: часто задаваемые вопросы. Продолжение

Рубрика: Программирование /  Программирование

Василий Гусев

PowerShell: часто задаваемые вопросы
Продолжение

В своей предыдущей статье [1] я уже ответил на многие популярные вопросы о PowerShell и о некоторых моментах работы с ним. Но, конечно, в рамках одной статьи сложно рассказать обо всём, поэтому продолжим.

Как вывести в строке какие-либо переменные или свойства объекта?

Думаю, все знают, как в PowerShell вывести строку на экран:

PS> "Hello world!"

Hello world!

И как объединить несколько строк или переменных в одну строку тоже:

PS> $w = "World"

PS> "Hello " + $w + "!"

Hello world!

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

PS> "Hello $w!"

Hello world!

Логично, не правда ли? Но зачастую в строку надо вставить не простую переменную, а свойство какого-либо объекта, и тут возникает проблема:

PS> $file = Get-Item C:\test.zip

PS> "Размер файла $file составляет $file.Length байт"

Размер файла C:\test.zip составляет C:\test.zip.Length байт

Здесь я поместил в переменную $file объект, представляющий файл test.ps1, и затем пару раз упомянул его в строке. В первом вхождении переменная $file была преобразована в полный путь к файлу (всё равно как если бы мы выполнили метод $file.toString()). А во втором случае… Произошло то же самое! PowerShell посчитал, что .Length – это часть строки, не имеющая никакого отношения к переменной $file. Его тоже можно понять – вдруг пользователь пропустил пробел между предложениями? Но что же делать, если нам всё-таки надо поместить значение свойства в строку, а использовать временные переменные или использовать конкатенацию с помощью кучи плюсов не хочется? В таком случае нужно использовать конструкцию $(). Её можно вставить в строку, а между скобок поместить любое выражение. Это выражение будет выполнено, и его результат будет подставлен в строчку:

PS> "Размер файла $file составляет $($file.Length) байт"

Размер файла C:\test.zip составляет 1364964 байт

Неплохо? Но это еще не всё. Я не зря сказал, что внутрь $() можно поместить любое выражение, это действительно так:

PS> "Размер: $([Math]::Round($file.Length / 1kb)) килобайт"

Размер: 1333 килобайт

Тут я поделил размер файла в байтах на встроенную константу 1kb, и затем, воспользовавшись методом [Math]::Round() из .Net Framework, округлил полученный результат до целых. Есть и другой способ – воспользоваться оператором форматирования -f.

PS> "Размер: {0:n3} мегабайт" -f ($file.Length / 1mb)

Размер: 1,302 мегабайт

Те, кто знаком с программированием на языке C#, наверняка обрадуются знакомому синтаксису (http://msdn2.microsoft.com/en-us/library/fbxft59x.aspx). Для остальных же поясню:

Конструкция {0:n3} состоит из нескольких частей, первая из них – «0», это индекс элемента во втором операнде. В данном случае он один, но можно указать и несколько элементов, и при расстановке их внутри строки будет использоваться их порядковый номер, начиная с 0 у первого. Вторая часть конструкции – «n», указывает на то, что значение необходимо отформатировать как число (number), ну а следующая за нею цифра (в данном случае «3») определяет количество знаков после запятой.

Естественно, возможности оператора форматирования -f не ограничиваются обрезкой лишних знаков после запятой, к примеру, он обладает огромными возможностями форматирования дат. В следующем примере с помощью -f я получу путь к файлу, составленный из текущего каталога и сегодняшней даты:

PS> "{0}\{1:yyyy-MM-dd}.bak" -f $pwd, (Get-Date)

C:\backups\2008-04-12.bak

А какие в PowerShell маскирующие символы?

К счастью, этим маскирующим символом не является обратный слеш «\», как во многих языках программирования. Если бы так было в PowerShell, мы бы замучились набирать пути файловой системы, повторяя каждый слеш дважды.

В PowerShell роль маскирующего символа выполняет «`» – апостроф, символ, расположенный на большинстве клавиатур на клавише «Ё», под тильдой. С его помощью можно маскировать любые символы:

PS> "`$pwd = $pwd"

$pwd = C:\root

Здесь я замаскировал символ «$» в первом упоминании переменной, и она не была преобразована в значение. Еще можно использовать маскирующий символ для обозначения специальных символов. Так, например, «`n» будет означать переход на следующую строку:

PS> "Первая строка`nВторая строка"

Первая строка

Вторая строка

Вот некоторые из часто употребляемых специальных символов:

  • `n – новая строка.
  • `a (alert) – этот символ заставляет спикер компьютера издавать писк. Бывает полезно для того, чтобы привлечь внимание пользователя.
  • `t – символ табуляции.

В чем отличия между разными типами кавычек в PowerShell?

Начнем с самых простых и популярных – двойных кавычек. В PowerShell, как и во множестве других языков, они служат для ограничения и обозначения строк. Все знают, что если набрать в командной строке PowerShell текст в кавычках, то он будет выведен на экран. Ну и, конечно, можно присвоить это текстовое значение переменной. Но, кроме того, точно так же можно работать и с многострочными текстами. Если, не закрыв кавычек, нажать <Enter>, то командная строка PowerShell переведет курсор на новую строку, и продолжит ожидание ввода. Так будет продолжаться, пока вы не закроете кавычки:

PS> $hw = "Hello

>> World!"

>>

PS>

Символы «>>» тут лишь означают, что ввод продолжается, в саму переменную они помещены не будут. Когда вы захотите использовать такую конструкцию в скрипте, просто делайте переносы строки:

$SqlCommand = "BACKUP DATABASE [$Base]

TO DISK = '$Path'

WITH INIT"

Перейдем ко второму типу кавычек, к одинарным. Их основное отличие от двойных – это то, что, если поместить внутри них название переменной, оно не будет преобразовано в её значение. Это хорошо видно на следующем примере:

PS> $var = '$pwd = ' + "'$pwd'"

PS> $var

$pwd = 'c:\root'

 Имя переменной внутри одинарных кавычек осталось неизменным, а внутри двойных кавычек было подставлено значение переменной вместо её имени. А еще в этом примере видно, что один тип кавычек можно без проблем использовать внутри других кавычек, не волнуясь о какой-либо маскировке. То есть если вам необходимо составить строчку, внутри которой множество одинарных кавычек (к примеру, фильтр для WMI), то удобнее будет заключить эту строку в двойные кавычки, и наоборот.

Второе отличие одинарных кавычек от двойных – это игнорирование символа маскировки – «`»:

PS> 'Первая строка`nВторая строка'

Первая строка`nВторая строка

Впрочем, при необходимости можно использовать символ одинарной кавычки внутри строки, нужно повторить его дважды:

PS> 'Одинарная кавычка '' среди собратьев'

Одинарная кавычка ' среди собратьев

Но что делать, если в нашей строке используется множество кавычек обоих видов, например, если нужно поместить в переменную кусок кода PowerShell или SQL? Для такого случая предусмотрена специальная разновидность кавычек, специально предназначенная для многострочных текстов (так же называемая HereString):

PS> $MyCode = @'

>> $Proc = Get-Process explorer

>> $Message = 'Переменная $Proc содержит сведения о процессе

>> например, в "$Proc.Path" содержится ' + "'$Path'."

>> '@

>>

PS> $MyCode

$Proc = Get-Process explorer

$Message = 'Переменная $Proc содержит сведения о процессе

например, в "$Proc.Path" содержится ' + "'$Path'."

Разумеется, есть и вариант HereString для двойных кавычек, в нём переменные преобразовываются в свои значения.

Как посчитать количество возвращенных командой объектов?

Очень часто встречающийся вопрос. Большинство командлетов в PowerShell в качестве результата возвращают несколько объектов, объединенных в массив. Ну и, разумеется, очень часто хочется посчитать количество этих результатов. Сделать это очень просто, достаточно приставить в конец конвейера командлет Measure-Object. Например, вот так можно посчитать количество журналов событий в системе:

PS> Get-EventLog -List | Measure-Object

Count : 11

Average :

Sum :

Maximum :

Minimum :

Property :

Думаю, многим интересно, что это за строки – Average, Sum и т. д. Дело в том, что возможности Measure-Object не ограничиваются подсчетом количества элементов (хотя по умолчанию делает только это). Он может производить и некоторые другие вычисления, причем не только над самими объектами, но и над их свойствами:

PS> Get-Process | Measure-Object -Property WS -Sum -Average

Count : 77

Average : 13063952,6233766

Sum : 1005924352

Maximum :

Minimum :

Property : WS

Так мы получили данные об используемой памяти (WorkingSet) – среднее значение на процесс и сумму по всем процессам.

Но вернёмся к нашему вопросу. Кроме использования Measure-Object, есть и другой способ, зачастую более удобный. У всех массивов в PowerShell есть свойство .Count, в котором и содержится количество элементов массива. Вот пример, как его можно использовать:

PS> $Shares = Get-WmiObject Win32_Share

PS> $shares.Count

5

Конечно, можно обойтись и без временной переменной, достаточно заключить выражение в скобки:

PS> (Get-WmiObject Win32_Share).count

5

PS> (Get-Process | where {$_.path -like "c:\win*"}).count

46

Здорово выполнять команды интерактивно в консоли или запускать из неё скрипты. Но для многих административных задач необходимо запускать скрипты из планировщика заданий. Как это сделать?

Сначала еще раз напомню про необходимость разрешить в системе выполнение неподписанных скриптов. Хоть это и всем известный шаг, при переходе в производственную среду о нем многие забывают. Либо, если вы серьезно относитесь к безопасности, стоит подумать о том, чтобы подписывать скрипты PowerShell, выполняющиеся на серверах. Обо всём этом можно подробнее прочитать, выполнив команду:

PS> Get-Help About_Signing

Ну а чтобы вызвать скрипт из планировщика, надо лишь в качестве запускаемой программы указать PowerShell.exe (полный путь – C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe), а в качестве аргумента – путь к файлу скрипта.

Если выполнить PowerShell.exe с ключом «/?», то можно узнать и о других полезных аргументах. Я опишу лишь ключи, полезные для автоматизированного запуска скриптов:

  • -NoLogo – не будет выводиться приветственная строка;
  • -NoProfile – не загружать профили PowerShell;
  • -NonInteractive – не выдавать запросов пользователю, т.е. при вызове, например Read-Line, произойдет ошибка, и выполнение скрипта будет продолжено;
  • -Command – указывается код PowerShell, выполняемый при запуске. Кстати, тут может быть не просто скрипт, но и полноценная команда.

У командлетов зачастую весьма длинные названия аргументов. Но иногда они вообще не указываются. Как это работает?

Возьмем в качестве образца командлет Set-Content. Полный его синтаксис предполагает следующую конструкцию:

PS> Set-Content -Path test.txt -Value "123" -Verbose

Тут мы говорим командлету поместить значение «123» в файл test.txt. Но обязательно ли указывать названия параметров Path и Value?

Если выполнить команду:

PS> Get-Help Set-Content -Parameter Path

то можно увидеть, что у этого параметра свойство Position равно 1. Это же свойство у параметра Value того же командлета равно 2. Это означает, что если мы не будем указывать имен параметров, то PowerShell посчитает первый аргумент значением для Path, а второй для Value. То есть можно выполнять команду вот так:

PS> Set-Content test.txt "123"

Зачем же тогда вообще может понадобиться указывать имена параметров, если всё работает и без них? Ну, во-первых, скрипт с полными именами параметров будет куда читабельнее, чем без них. Те, кто хоть раз ломал голову над своим скриптом многолетней давности, оценят. А во-вторых, если мы указываем названия параметров, – нам не нужно помнить их порядок, он может быть любым:

PS> Set-Content -Value "123" -Path test.txt

Но и в этом случае незачем писать полные имена параметров. Возьмем теперь команду с действительно длинными параметрами – Write-Host. У неё есть два параметра, позволяющие задать цвета выводимого текста и его фона: ForegroundColor и BackgroundColor. И свойство Position у этих параметров равно «Named» (в чем можно убедиться, выполнив команду:

PS> Get-Help Write-Host -Parameter ForegroundColor)

Named в данном случае означает, что для использования параметра необходимо указать его имя, вариант с помещением аргументов в правильном порядке не сработает. Но и полное имя указывать не обязательно, достаточно указать лишь первые несколько букв, чтобы PowerShell смог отличить имя параметра от остальных:

PS> Write-Host test -f red -b blue

test

Впрочем, чтобы было несколько понятнее, можно написать и так:

PS> Write-Host test -fore red -back blue

test

Но если мы, например, попробуем найти все команды, работающие с процессами, используя Get-Command, и вместо параметра -Noun укажем -n, то нас ждет сообщение об ошибке:

PS> Get-Command -n process

Get-Command : Не удается обработать параметр, так как имя параметра "n"

неоднозначно. Возможные совпадения: -Name -Noun.

В строка:1 знак:12

+ Get-Command <<<< -n process

Дело в том, что в этом случае параметры отличаются со второй буквы, и для того чтобы PowerShell смог разобраться, какой параметр подразумевается, придется указывать на одну букву больше. В данном случае будет достаточно использовать -no.

Как получить значения параметров ключа реестра или, наоборот, задать их?

Несмотря на то что работа с реестром из PowerShell кажется очень простой, всё же существуют некоторые тонкости. Думаю, всем уже известно, что в PowerShell используется система так называемых «поставщиков» (provider), позволяющих работать с иерархическими системами хранения данных, как с обычной файловой системой (и даже автодополнение с помощью клавиши табуляции там тоже работает). И реестр как раз представлен в виде такого провайдера. Это дает возможность использовать для навигации по нему всё те же команды, как и для файловой системы: dir (Get-ChildItem), cd (Set-Location) или pwd (Get-Location). Но только этих команд для работы с реестром будет недостаточно.

Например, если нам понадобится посмотреть список автоматически запускаемых программ из HKCU:\Software\Microsoft\Windows\CurrentVersion\run, то одним Dir не обойтись. Дело в том, что параметры ключа не являются дочерними элементами по отношению к ключу. Они представляют собой его свойства, и для того чтобы получить их список, придётся использовать команду Get-ItemProperty (или её псевдоним – «gp»):

PS> gp HKCU:\Software\Microsoft\Windows\CurrentVersion\Run

Sidebar : C:\Program Files\Windows Sidebar\sidebar.exe /autoRun

MsnMsgr : "C:\Program Files\Windows Live\Messenger\MsnMsgr.Exe" /background

FolderShare : "C:\Program Files\FolderShare\FolderShare.exe" /background

Skype : "C:\Program Files\Skype\Phone\Skype.exe" /nosplash /minimized

WMPNSCFG : C:\Program Files\Windows Media Player\WMPNSCFG.exe

 PowerShell попытается самостоятельно подобрать наилучший метод форматирования, основываясь на количестве свойств объекта. Так, если у вас в этом ключе реестра менее 5 параметров, то они будут выведены в виде таблицы с параметрами в роли столбцов. Чтобы этого избежать, следует перенаправить вывод в командлет Format-List, используя конвейер. Для создания параметра используется командлет New-ItemProperty или Set-ItemProperty (псевдоним – «sp»):

PS> cd HKCU:\Software\Microsoft\Windows\CurrentVersion\Run

PS> sp -Path . -Name "Моя утилита" -Value "c:\ myutil.exe"

Тут я сначала установил в качестве текущего каталога ключ реестра, использовав cd (Set-Location). Затем с помощью командлета Set-ItemProperty создал параметр «Моя утилита» со значением «c:\myutil.exe» в текущем ключе (точка в качестве пути обозначает текущий каталог). Теперь можно проверить результат, снова использовав Get-ItemProperty, но на этот раз, указав ему конкретное свойство:

PS> gp . "Моя утилита" | Format-List

Моя утилита : c:\utils\myutil.exe

Ну и для завершения примера удалим этот созданный ключ. Не сложно догадаться, что для этого понадобится командлет «Remove-ItemProperty (“rp”)»:

PS> rp . "Моя утилита"

Как импортировать данные из Excel или, наоборот, поместить данные из PowerShell в Excel?

К сожалению, с PowerShell не поставляются командлеты для непосредственного импорта и экспорта файлов XLS. Но выход есть, и даже не один. Можно использовать для обмена данными с Excel файлы с разделителями запятыми – csv (Comma Separated File). По умолчанию этот формат даже открывается с помощью Excel и, разумеется, он может в него сохранять. Файлы CSV импортируются и экспортируются из PowerShell с помощью командлетов Import-Csv и Export-Csv соответственно.

Интересный момент – при импорте из csv-файла данные из первой его строки будут считаться заголовками, и в результате будут созданы объекты с такими же названиями свойств. Предположим, у нас есть файл следующего вида:

Имя,Отчество,Фамилия

Иван,Иванович,Иванов

Пётр,Петрович,Петров

Сидор,Сидорович,Сидоров

Импортировав этот файл, мы получим массив из трёх объектов, обладающих свойствами «Имя», «Отчество», и «Фамилия»:

PS> $fio = Import-Csv fio.csv

PS> $fio[0]

Имя Отчество Фамилия

--- -------- -------

Иван Иванович Иванов

PS> $fio | foreach {$_.Фамилия}

Иванов

Петров

Сидоров

Ну и, конечно, если очень хочется работать напрямую с файлами XLS и XLSX, то можно воспользоваться сторонними командлетами, например, бесплатной оснасткой PowerData, которую можно скачать по адресу http://www.ultimate-projects.ru. Кроме командлетов Import-Excel и Export-Excel, в комплект входит Invoke-SQL для выполнения SQL‑запросов и получения результатов в виде объектов PowerShell.

Каким образом в PowerShell можно перехватить ошибку?

Хорошие возможности в области обработки ошибок, несомненно, являются очень важным фактором для скриптового языка при применении в рабочем окружении. К счастью, у PowerShell в этом плане всё обстоит прекрасно. Есть и автоматические параметры для всех командлетов – ErrorAction и ErrorVariable, позволяющие определить поведение команды в случае ошибки и поместить объект ошибки в указанную переменную. Присутствуют специальные переменные – $ErrorActionPreference (глобально задает реакцию на ошибки) и $Error, содержащая массив последних произошедших ошибок (самая последняя $Error[0]). Но одним из самых полезных средств, конечно, является ключевое слово «trap». После этого слова задается скриптовый блок, который будет выполнен в случае ошибки. Кроме этого, в том же блоке можно обратиться к объекту ошибки (который внутри этого блока будет находится в переменной $_) и указать дальнейшие действия – break (прервать выполнение) или continue (продолжить выполнение скрипта дальше).

PS> trap {echo "Ошибка: $_"; break}; 1; 2/$null; 3

1

Ошибка: Попытка деления на нуль.

Попытка деления на нуль.

At line:1 char:39

+ trap {echo "Ошибка: $_"; break}; 1; 2/ <<<< $null; 3

Как видно из примера, после того как произошла ошибка деления на ноль, отработал код, указанный после ключевого слова trap, и затем выполнение скрипта было прервано. В случае же если указать continue, то после ошибки будут выполнены последующие команды:

PS> trap {echo "Ошибка: $_"; continue}; 1; 2/$null; 3

1

Ошибка: Попытка деления на нуль.

3

Trap очень удобен для применения в скриптах. Его можно поместить, к примеру, в начале файла, применив в скриптовом блоке командлет Export-Clixml для сохранения объекта ошибки в XML-файл:

trap {$_ | Export-Clixml Error.xml; stop}

Затем, при анализе причин проблемы, можно загрузить ошибку из этого файла в объект и детально разобраться в причинах:

PS> $Err = Import-Clixml Error.xml

PS> $Err

Copy-Item : Не найдено сетевое имя.

At line:1 char:10

+ copy-item <<<<  file.txt \\server\share

PS> $Err.InvocationInfo

MyCommand             : Copy-Item

CommandLineParameters : {Destination, Path}

ScriptLineNumber      : 1

OffsetInLine          : 10

ScriptName            :

Line                  : copy-item file.txt \\server\share

PositionMessage       :

                        At line:1 char:10

                        + copy-item <<<<  file.txt \\server\share

InvocationName        : copy-item

PipelineLength        : 1

PipelinePosition      : 1

ExpectingInput        : False

CommandOrigin         : Runspace

 Жду новых вопросов на адрес. Ну и, конечно, заходите на мой блог – http://xaegr.wordpress.com.

Помните! Никакой FAQ не заменит чтения документации, так что для использования всей мощи PowerShell, надо знать команду Get-Help, и ознакомиться с содержимым прилагающейся к PowerShell документации.

  1. Гусев В. PowerShell: часто задаваемые вопросы. //Системный администратор, №3, 2008 г. – С. 16-22.

Комментарии
 
  26.01.2018 - 09:37 |  Victor_VM

Здравствуйте, подскажите пожалуйста, есть такая задача, каждые 2 часа на экран пользователей в домене выводить текст.
Я написал на VBS скриптик, в гпо создал задачу и поместил в неё скрипт, но он не отрабатывает на ПК с Win 8 и Win 10, предполагаю, что из-за безопасности, можно написать такой же скрипт на PS? Как это сделать? Ниже прилагаю то, как я сделал на VBS.
MsgBox "Уважаемые коллеги, отдохните 10 минут." & vbCrLf & "В здоровом теле, здоровый дух!"

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru