Jump Start в PowerShell (часть II)

  • Tutorial
Кто, если не ты? Когда, если не сейчас?



В предыдущей части мы научились запускать PowerShell, разобрались с политикой выполнения сценариев. Поняли, что такое командлеты, знаем, как передавать их по конвейеру и как получить их свойства. Узнали, что PowerShell имеет огромный Help.

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

← Перейти к I части

Введение



Начать я хочу с ответов на вопросы, возникшие в первом нашем с Вами Jump Start'е:

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

Изучить PowerShell точно стоит, если ты администрируешь Windows Server и серверное ПО от Microsoft. Возьмём, к примеру, один из популярнейших продуктов Microsoft Exchange Server — и тут же натыкаешься на PowerShell консоль Exchange Management Shell.
Зная PoSh ты без труда сможешь сделать любую нужную выгрузку данных, настроить контроль за любыми процессами системы, отправлять отчёты, создавать пользователей, решать их проблемы…

Я не занимаюсь администрированием, что тогда?
Это можно сравнить с тем, как люди совершенно разных профессий увлекаются созданием web-сайтов. Вроде им это очень и не надо, но они этим занимаются. Ради собственного развития, развлечения или удовольствия.
Вы студент? Напишите себе своё первое приложение, которое будет решать квадратное уравнение.
Или, например, изучите парсинг и сделайте своё приложение для скачивания музыки или красивых видео из youtube.
Всё зависит от тебя и твоей фантазии. Посмотрите сюда, PowerShell можно даже научить разговаривать.

Можно же просто посмотреть видео на MVA!
Да, я совершенно согласен с Вами — это может быть эффективнее.
Правда, не каждый имеет время на просмотр 12-15 часового курса и не каждый может так воспринимать информацию.
Здесь я пробую дать базовые знания с которыми можно будет успешно продолжить развивать свои умения безгранично дальше.


Переменные



  • Переменная в PowerShell начинается со знака $ (в Евросоюзе со знака €) и в названии может содержать любые буквы, цифры и символ подчёркивания.
  • Чтобы назначить переменной значение, достаточно его ей присвоить знаком "=". Чтобы вывести значение переменной, можно просто написать эту переменную. Вывод информации мы разберём по тексту ниже.

    $var = 619
    $var
    




  • Над числами можно производить арифметические операции, строки можно складывать. Если к строке прибавить число, число автоматически преобразуется в строку.

    $a = 1
    $b = 2
    $c = $a + $b
    $c   #c = 3
    
    $str = "Хабра"
    $str = $str + "хабр"
    $str   #str = Хабрахабр 
    
    $str = $str + 2014
    $str   #str = Хабрахабр2014
    
    


  • Если нам надо узнать, какой тип имеет та или иная переменная, можно воспользоваться методом GetType()

    $s = "Это строка?"
    $s.GetType().FullName
    




  • Тип переменной PowerShell определяет автоматически или его можно назначить вручную.

    $var = "one"
    [string]$var = "one"
    


    PowerShell использует типы данных Microsoft .NET Framework. Рассмотрим основные:

    Тип / .NET класс Описание
    [string]

    System.String
    Строка

    $var = "one"
    

    [char]

    System.Char
    Символ

    $var = [char]0x263b
    

    [bool]

    System.Boolean
    Булево. Может иметь значение $true или $false.

    $bvar = $true
    

    [int]

    System.Int32
    32-разрядное целое число

    $i = 123456789 
    

    [long]

    System.Int64
    64-разрядное целое число

    $long_var = 12345678910
    

    [decimal]

    System.Decimal
    128 битное десятичное число. Буква d на конце числа обязательна

    $dec_var = 12345.6789d
    

    [double]

    System.Double
    8-байтное десятичное число с плавающей точкой

    [double]$double_var = 12345.6789
    

    [single]

    System.Single
    32 битное число с плавающей точкой

    [single]$var = 123456789.101112
    

    [DateTime]

    System.DateTime
    Переменная даты и времени.

    $dt_var = Get-Date
    

    [array]

    System.Object[]
    Массив. Индекс элементов массива начинается с 0 — чтобы обратиться к первому элементу массива $mas, следует написать $mas[0].

    $mas = "one", "two", "three"; 
    


    Для добавления элемента к массиву, можно записать

    $mas = $mas + "four"
    


    Каждый элемент массива может иметь свой тип

    $mas[4] = 1
    


    [hashtable]

    System.Collections.Hashtable
    Хеш-таблицы. Различие между хэш-таблицами и массивами в том, что в массивах используются индексы, а в хэш-таблице именованные ключи. Хеш-таблицы строятся по принципу: @{ ключ = «значение» }

    $ht = @{odin="one"; dva="two"; tri="three"} 
    




    Чтобы добавить элемент к хеш-таблице, можно или присвоить ей тот ключ, которого ещё нет, или воспользоваться методом Add(). Если присваивание делать к существующему ключу — значение ключа изменится на присваиваемое. Для удаления элемента из хеш-таблицы есть метод Remove().

    $ht.Add("chetyre", "four")
    $ht.pyat = "five"
    $ht.Remove("dva")
    







Вы наверняка уже поняли, что в переменную можно записать не только какое-то определённое значение и заметили в таблице класс System.Object[]. В переменную так же можно записать вывод любого командлета.

Разберём боевую задачку: требуется узнать IP-адрес и MAC-адрес нескольких компьютеров в сети. Имена компьютеров domain-comp1, domain-comp2. Вариант решения под спойлером:
Показать код

$MAC = Get-WmiObject -ComputerName domain-comp1, domain-comp2 -Class Win32_NetworkAdapterConfiguration -Filter IPEnabled=True  |  Select-Object -Property * | SELECT PSComputerName, @{Name="IPAddress";Expression={$_.IPAddress.get(0)}}, MACAddress, Description
$MAC

Что происходит:

Мы записываем в переменную $MAC результат от выполнение командлета Get-WmiObject, с помощью которого передаём WMI запрос на компьютеры, указанные в параметре -computername и возвращаем нужную нам информацию из класса Win32_NetworkAdapterConfiguration.
Обратите внимание, если надо получить IP-адрес и MAC-адрес текущего компьютера, то вместо имён компьютеров, параметру -computername мы передадим точку. И, давайте, в следующим примере сохраним результат в файл D:\comp_mac_ip.csv:
$MAC = Get-WmiObject -ComputerName . -Class Win32_NetworkAdapterConfiguration -Filter IPEnabled=True  |  Select-Object -Property * | SELECT PSComputerName, @{Name="IPAddress";Expression={$_.IPAddress.get(0)}}, MACAddress, Description
$MAC | Export-Csv D:comp_ip_mac.csv -Encoding UTF8




Область действия переменных


Область действия переменной в PowerShell может быть либо локальной, либо глобальной. По умолчанию переменная имеет локальную область действия и ограничена областью действия, например, доступна только в функции или только в текущем сценарии. Глобальная переменная действует во всём текущем сеансе PowerShell. Для того, чтобы обозначить глобальную переменную, достаточно написать следующую конструкцию: $Global: переменная = значение

$Global:var = 12


И ещё фишка по теме переменных… Как-то, обсуждая с коллегой 1С и его русскоязычную среду программирования, задались вопросом:
— Интересно, а если в PowerShell задать функцию или переменную на русском языке, она будет работать?
— По идее должно работать, это же тоже символы.

И да, действительно:

function привет() { "Привет, $env:USERNAME. Может не стоит писать на русском?"
                    Start-Sleep 3
                    "Открыть Хабр?"
                    }
function открывай() { & "C:\Program Files\Internet Explorer\iexplore.exe" http://habrahabr.ru/post/242425/ }







Операторы сравнения и логические операторы



Операторы сравнения и логические операторы проверяют равенство или соответствие между двумя значениями.

  • При выполнении условия, конструкция сравнения всегда возвращает логическое значение $true или $false, если условие ложь.


В таблице ниже приведены операторы сравнения:

Оператор Описание Пример
-eq Equal / Равно (=)
$var = "619"
$var -eq 123   #$false

-ne Not equal / Не равно (<>)
$var = "619"
$var -ne 123   #$true
-gt Greater than / Больше (>)
$var = "619"
$var -gt 123   #$true
-ge Greater than or equal / Больше или равно (>=)
$var = "619"
$var -ge 123   #$true
-lt Less than / Меньше (<)
$var = "619"
$var -lt 123   #$false
-le Less than or equal / Меньше или равно (<=)
$var = "619"
$var -le 123   #$false
-like Сравнение с учётом символа подстановки
"Habra" -like "habr*"   #true
-notlike Сравнение с учётом не соответствия символа подстановки
"Habra" -notlike "habr*"   #false
-contains Содержит ли значение слева значение справа
1, 2, 3, 4, 5 -contains 3   #$true
-notcontains Если значение слева не содержит значение справа, получим истину
1, 2, 3, 4, 5 -notcontains 3   #$false
-match Использование регулярных выражений для поиска соответствия образцу
$str = "http://habrahabr.ru"
$str -match "^http://(\S+)+(.ru)$"   #$true
-notmatch Использование регулярных выражений для поиска несоответствия образцу
$str = "http://habrahabr.ru"
$str -notmatch "^http://(\S+)+(.com)$"   #true
-replace Заменяет часть или все значение слева от оператора
"Microhabr" -replace "Micro","Habra"   #Habrahabr


Разберём пример. В примере мы формируем путь до возможного профиля пользователя на удалённом компьютере в зависимости от операционной системы. Решение под спойлером:
Показать код

#Путь к профилю на удалённом компьютере
Function UProfile ($UCompName, $ULogin) {
        [string]$CompOS = Get-WmiObject -ComputerName $UCompName -Class Win32_OperatingSystem | SELECT Caption   #Получаем заголовок операционной системы компьютера
    if ($CompOS -match "Windows 7") {   #Если в заголовке содержится "Windows 7"
        [string]$LinkUserProfile = "\\$UCompName\d$\users\$ULogin"   #Получаем такой путь
        $LinkUserProfile  
    } elseif ($CompOS -match "Windows XP") {   #Если в заголовке содержится "Windows XP"
        [string]$LinkUserProfile = "\\$UCompName\d$\Documents and Settings\$ULogin"   #Получаем такой путь
        $LinkUserProfile        
    }
}

UProfile domain-comp1 Administrator   #функция имя-компьютера логин-пользователя


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

#Путь к профилю на удалённом компьютере
Function UProfile ($UCompName, $ULogin) {
        [string]$CompOS = Get-WmiObject -ComputerName $UCompName -Class Win32_OperatingSystem | SELECT Caption   #Получаем заголовок операционной системы компьютера
    if ($CompOS -match "Windows 7") {   #Если в заголовке содержится "Windows 7"
        [string]$LinkUserProfile = "\\$UCompName\d$\users\$ULogin"   #Получаем такой путь
    } elseif ($CompOS -match "Windows XP") {   #Если в заголовке содержится "Windows XP"
        [string]$LinkUserProfile = "\\$UCompName\d$\Documents and Settings\$ULogin"   #Получаем такой путь    
    }
    if (Test-Path $LinkUserProfile) { $LinkUserProfile } else { "Профиль $ULogin не существует на компьютере $UCompName" }   #Проверяем существование профиля
}

UProfile domain-comp1 Administrator   #функция имя-компьютера логин-пользователя



Кстати, регулярные выражение в PowerShell очень хорошо описал Xaegr в своём блоге. Стоит того, чтобы потратить пару часов на прочтение и постичь умение написания regular expressions.

  • Можно дополнительно задать условие, если при сравнении следует учитывать регистр. Для этого следует перед оператором подставить букву "c"

    $str1 = "Habr"
    $str2 = "habr"
    $str1 -ceq $str2   #$false
    


  • Можно использовать несколько операторов сравнения, применяя логические операторы. Логические операторы перечислены в таблице ниже:


Оператор Описание Пример
-and Логическое и
("Строка" -eq "Строка") -and (619 -eq 619)   #$true
-or Логическое или
("Строка" -eq "Строка") -or (619 -eq 123)   #$true
-not Логическое не
-not (123 -gt 324)   #$true
или
!(123 -gt 324)   #$true


Пара слов про ifelseif и else

if ( условие1-верно ) { выполняем-код }   #если условие 1 верно, выполняем код, если нет - идём дальше
elseif ( условие2-верно ) { выполняем-код }   #необязательное условие: иначе, если условие 2 верно, выполняем код
else { выполняем-код }  #необязательное условие: если прошлое условие не верно, выполняем код


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

$str = ""
if ( 1 -gt 2 ) { $str = "Апельсин" } 
elseif ( $str -eq "Апельсин" ) { $str } 
else { $str = "Яблоко"; $str } 

Ответ



Вывод информации



Рассмотрим несколько основных вариантов вывода информации на экран:
  • Запись в файл, используя Out-File

    $str = "Hello Habr!"
    $str | Out-File D:\out_test1.txt
    

    или, как мы говорили в I части, использовать знак ">"

    $str = "Hello Habr!"
    $str > D:\out_test2.txt
    

  • Экспорт данных в .csv файл, используя Export-Csv

    $p = Get-Process
    $p | SELECT Name, Path | Export-Csv -Path D:\out_test3.csv
    

  • Запись в файл HTML, используя ConvertTo-Html

    $h = Get-Process
    $h | ConvertTo-Html -Property Name, Path > D:\out_test4.html
    & D:\out_test4.html   #Откроем файле после формирования
    




    Работать с HTML выводом на самом деле очень приятно. Имея навыки web-программирования можно добавить стилей и на выходе получить очень симпатичные отчёты.


Прочитать файл нам поможет уже знакомый нам командлет Get-Content

Get-Content D:\out_test1.txt




Циклы


Циклы в PowerShell мало чем отличаются от циклов в других языках. Почти всё как и везде:

Do While

Делать до тех пор, пока условие верно

$x = 10

do
{
    $x
    $x = $x - 1    
}
while ($x -gt 0)


Do Until

Цикл, который повторяет набор команд, пока выполняется условие

$x = 0

do
{
    $x
    $x = $x + 1    
}
until ($x -gt 10)


For

Цикл, который повторяет одинаковые шаги определённое количество раз

for ($i = 0; $i -lt 10; $i++)
{ 
    $i
}


ForEach

Цикл по коллекции объектов

$collection = "Имя-Компьютера1", "Имя-Компьютера2", "Имя-Компьютера3"
foreach ($item in $collection)
{
    $item 
}


Пишем функции



  • Функции в PowerShell имеют следующую структуру:

    function имя-функции ([параметр1, ..., параметрN]) 
         { 
            тело-функции 
         } 
    

  • Функции не обязательно должны возвращать результат.
  • При вызове функции, все параметры разделяются пробелами.


Например, функция для получения квадрата числа, на PowerShell будет выглядеть так:

function sqr ($a)
     {
      return $a * $a
     }

sqr 2   #Результат: 4


И на последок, пример функции, которая поможет нам выполнять транслитерацию. В этом примере как раз всё то, что мы сегодня рассматривали:
Показать код

#Транслитерация
function Translit ([string]$inString)
{
    $Translit = @{    #Создаём хеш-таблицу соответствия символов
    [char]'а' = "a"
    [char]'А' = "A"
    [char]'б' = "b"
    [char]'Б' = "B"
    [char]'в' = "v"
    [char]'В' = "V"
    [char]'г' = "g"
    [char]'Г' = "G"
    [char]'д' = "d"
    [char]'Д' = "D"
    [char]'е' = "e"
    [char]'Е' = "E"
    [char]'ё' = "yo"
    [char]'Ё' = "Yo"
    [char]'ж' = "zh"
    [char]'Ж' = "Zh"
    [char]'з' = "z"
    [char]'З' = "Z"
    [char]'и' = "i"
    [char]'И' = "I"
    [char]'й' = "j"
    [char]'Й' = "J"
    [char]'к' = "k"
    [char]'К' = "K"
    [char]'л' = "l"
    [char]'Л' = "L"
    [char]'м' = "m"
    [char]'М' = "M"
    [char]'н' = "n"
    [char]'Н' = "N"
    [char]'о' = "o"
    [char]'О' = "O"
    [char]'п' = "p"
    [char]'П' = "P"
    [char]'р' = "r"
    [char]'Р' = "R"
    [char]'с' = "s"
    [char]'С' = "S"
    [char]'т' = "t"
    [char]'Т' = "T"
    [char]'у' = "u"
    [char]'У' = "U"
    [char]'ф' = "f"
    [char]'Ф' = "F"
    [char]'х' = "h"
    [char]'Х' = "H"
    [char]'ц' = "c"
    [char]'Ц' = "C"
    [char]'ч' = "ch"
    [char]'Ч' = "Ch"
    [char]'ш' = "sh"
    [char]'Ш' = "Sh"
    [char]'щ' = "sch"
    [char]'Щ' = "Sch"
    [char]'ъ' = ""
    [char]'Ъ' = ""
    [char]'ы' = "y"
    [char]'Ы' = "Y"
    [char]'ь' = ""
    [char]'Ь' = ""
    [char]'э' = "e"
    [char]'Э' = "E"
    [char]'ю' = "yu"
    [char]'Ю' = "Yu"
    [char]'я' = "ya"
    [char]'Я' = "Ya"
    }
    $TranslitText = ""
    foreach ($CHR in $inCHR = $inString.ToCharArray())
        {
        if ($Translit[$CHR] -cne $Null) 
            { $TranslitText += $Translit[$CHR] }   #аналог записи $TranslitText = $TranslitText + $Translit[$CHR] 
        else
            { $TranslitText += $CHR }
        }
    return $TranslitText
}

Translit Хабрахабр   #Вывод: Habrahabr



Послесловие



Теперь мы знаем достаточно информации, чтобы начать погружаться в PowerShell ещё глубже. В этой части мы встретились с переменными, типами данных, разобрались с выводом информации, операторами сравнения, циклами… Посмотрите, ведь начать, оказывается, не так уж и сложно!

Сохраняй спокойствие и учи PowerShell.

Продолжение следует...



Дополнительная информация



Поделиться публикацией

Комментарии 13

    0
    Возьмём, к примеру, один из популярнейших продуктов Microsoft Exchange Server

    Хорошее у вас «к примеру», Exchange — топовый продукт Microsoft в котором PoSH появился ещё до официального анонса :-)
      +2
      Моя основная претензия к powershell, ещё в те времена, когда он меня волновал, состояла в том, что для того, чтобы стать экспертом в powershell, надо быть экспертом в .net. Либо ты всё это знаешь насквозь, либо так и остаёшься тварью гуглящей по каждому чиху.
        +1
        ну не то что бы совсем экспертом, можно вполне не программировать под .NET, но успешно пользоваться PoSH. Хотя не отрицаю — знание .NET хорошо помогает, ибо по сути это скриптовый язык для .NET =)
        +1
        Не обязательно быть сис админом для использования PS. Если хоть как то используете Azure, то без него просто никак.
        Автору продолжать.
          +1
          А где цикл Foreach-Oject? :-)
          Он хоть и имеет алиас foreach, от одноименного цикла отличается тем, что принимает значения по конвейру, имеет расширенный синтаксис begin-process-end и отличается поведением break/continue.
            +1
            Для JumpStart будет очень полезно, если вы расскажете в подробностях про Pipeline PoSH, ибо очень важная и большая часть.
            И как это все интегрированно с .NET тоже полезно знать (но это уже не совсем фундаментальные основы).

            Продолжайте, неплохие материалы в итоге получатся.
              0
              А почему в примере "$var = [char]0x263b" 0x263 больше чем 0xFF? Опечатка, или в PoSh char реально больше 8 бит?
              +1
              Добрый день!
              В боевом примере " узнать IP-адрес и MAC-адрес" не выдает ни того, ни другого: столбцы без значений. Только имя компьютера и описание сетевого адаптера.
              И почему первая переменная использует функцию, остальные просто текстом?
              @{Name=«IPAddress»;Expression={$_.IPAddress.get(0)}}, MACAddress, Description


              PS благодарю за статьи! Стали волшебным пинком для старта изучения :)
                0
                Теперь работает.
                Сперва, печатал сам, маленькими буквами. Подумал регистр имеет значение, скопипастил ваш код.
                Не работает.
                А после перезагрузки компьютера все встало на места :)
                Спасибо!
                  0
                  И почему первая переменная использует функцию, остальные просто текстом?
                  @{Name=«IPAddress»;Expression={$_.IPAddress.get(0)}}, MACAddress, Description

                  Можно получить и просто свойство IPAddress, которое является массивом ip-адресов.
                  Если на сетевой плате включен протокол ipv6, то класс нам будет возвращать и его. Получим на выводе что-то вроде: {192.168.1.9, fe80::1542:4928:111d:d13a}
                  А так как нам вероятнее всего нужен будет только ipv4, мы поэтому и обращаемся к методу get и получаем первый элемент: $_.IPAddress.get(0)

                  @{Name=«IPAddress»;Expression={$_.IPAddress.get(0)}}

                  У нас идёт выборка Select и вот этой записью мы добавляем в неё свой столбец с названием IPAddress и в него для каждого элемента записываем результат выполнения метода get
                    0
                    Понял.
                    Исчерпывающе. Благодарю!

                    PS жду продолжение статей :)
                  0
                  Что-то не последовало продолжения, а жаль :)

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое