Работа с хеш таблицами в Powershell Hashtable с примерами


08 октября 2019


Использование хеш таблиц в Powershell Hashtable и создание на примерах

Хеш таблицы в Powershell или Hashtable это отдельный тип данных, который позволяет удобно хранить значения. Хэш таблицы похожи на словари в Python или формат JSON. Кроме всего этого мы можем передавать этот тип в виде параметров команд. Мы разберем каждую особенность работы с ними на примерах.

Этот тип так же называется коллекциями и ассоциативным массивом. Такой тип данных является более гибким инструментом, чем обычные массивы. Они могут дополнять друг друга при работе в консоли.

Основы по работе с хэш таблицами

Перед тем как перейти к описанию таблиц посмотрим как выглядит другой тип, который был подробно описан в предыдущей статье массивы и листы в Powershell.

Что такое массив

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

$array = @('число', 1, 'строка' )

Вывод этих данных построчный:

$array

Массивы Powershell

Чаще всего они передаются через foreach или вызываются по индексам:

$computers = @('AD1','AD2')
# Вызов по индексу
$computers[0]
# В цикле
foreach($computer in $computers)
{
    ping $computer
}

В массивах нельзя удалять и добавлять элементы, но можно делать обновление элементов, и сложение словарей (считайте добавление):

$array = @(1,2,3)
$array[0] = 'Заменили единицу'
$array[1] = ''
$array += 'Новое число'

Изменения массива в Powershell

Что такое hashtable

Такой тип данных как хэш таблицы вы встретите в любом языке программирования. Если в массивах мы используем только значение, то в хеш таблицах мы используем пару ключ и значение. 

Пустой hashtasble объявляется так:

$hashtable = @{}

Обратите внимание на различие скобок с массивами. 

На следующем примере мы добавим элемент в хэш таблицу выше:

$hashtable = @{}
# Name
$computer = 'Computer'
# Value
$name = 'AD1'
# Добавление в hashtable
$hashtable.add($computer,$name)
# Вызов
$hashtable

powershell хэш таблицы

В следующих примерах мы научимся получать элементы.

Получение и добавление элементов

В отличие от массивов, где мы получали элементов по индексам в hashtable это делается по ключам:

$hashtable['Computers']

получение данных хэш таблицы powershell

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

# Ключ
$user = 'User'
# Значение
$fname = 'Masha'
# Добавление
$hashtable[$user] = $fname
$hashtable

изменение данных hashtable powershell

Можно сложить две коллекции, но при условии что ключи не совпадают так как они должны быть уникальными:

$foo = @{'Street'='Leninskiy Prospekt'}
$foo += @{Zip = '78701'}

 

Создание хэш таблиц со значениями

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

$user = @{
		'User' = 'Alex';
		'Computer' = 'CL1';
		'Enabled' = $True;
}

создание hashtable powershell

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

 

Использование как справочной информации

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

$hosts = @{
		'AD1'='Домен контроллер';
		'SQ1'='База данных 1C по складу';
		(hostname)='Отсюда начнется скайнет';
		'PL1'='Не трогать пока работает';
	}
$current_server = $hosts[$env:COMPUTERNAME]
$current_server

вызов коллекции powershell

$env:COMPUTERNAME вернет имя компьютера из окружения и будет использоваться как ключ для получения данных из хэш таблицы. 

Вызов по нескольким ключам

Powershell позволяет вызывать несколько ключей одновременно:

$hosts[@('AD1','SQ1')]
$hosts[('AD1','SQ1')]
$hosts['AD1','SQ1']

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

вызов коллекции powershell и изменение

 

Итерации, конвейеры и циклы

Связи с тем, что hashtable отличаются от массивов наличием ключей итерации делаются иначе. 

Для примера, если мы передадим хэш таблицу через конвейер, то у нас отобразится количество объектов:

$hsh = @{'key'='val';'key2'='val'}
$hsh | Measure-Object

А если используем счетчик Count отобразится количество значений:

$hsh.count

Count powershell hash

Мы можем вызвать отдельно ключи:

$hsh.Keys

И отдельно значения:

$hsh.Values

Count powershell hashМожно передавать эти значения сразу либо вызывать из конвейера. Для примера у меня есть таблица с возрастом и я могу вызвать эти элементы несколькими способами через конвейер:

$data = @{'First'=10; 'Second'=20;'Third'=30}

# Вывод только Value
$data.Values | Write-Output

# Вывод только Name
$data.Keys | Write-Output

# Вывод через ForEach
$data | ForEach-Object {Write-Output $PSItem.Values}
$data | ForEach-Object {Write-Output $PSItem.Keys}

# Вызов из хэш таблицы по ключу
$data.Keys | ForEach-Object {
	$msg = 'Ключ {0} значение {1}' -f $PSItem,$data[$PSItem]
	Write-Output $msg
	}

# Сумма
$data.values | Measure-Object -Sum

конвейер powershell hashtableПеременная $PSItem аналогична такому написанию $_ .

Используя циклы:

foreach($item in $data.Keys){
$msg = 'Key {0} Value {1}' -f $Item,$data[$item]
Write-Output $msg
}

foreach($item in $data.Values){
$msg = 'Только Value {0}' -f $Item
Write-Output $msg
}

Подстановка данных powershell hashtable

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

GetEnumerator()

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

$data.GetEnumerator() | ForEach-Object {
	$message = 'Key {0} Value {1}' -f $_.key, $_.value
	Write-Output $message
	}

Подстановка данных powershell hashtable

Изменение используя циклы

В отличие от других языков мы не можем изменять коллекции Powershell в циклах и конвейерах. Для этого потребуется дополнительные действия.

$dict = @{'Key'='Value'}
foreach($el in $dict.keys){$dict[$el]='Value2'}
$dict.Keys | ForEach-Object {$dict[$PSItem]='Value2'}

В разных случаях будут разные ошибки:

  • Коллекция была изменена; невозможно выполнить операцию перечисления.
  • Произошла ошибка при перечислении элементов коллекции: Коллекция была изменена; невозможно выполнить операцию перечисления
  • Collection was modified; enumeration operation may not execute.
  • An error occurred while enumerating through a collection: Collection was modified; enumeration operation may not
  • execute

Что бы избежать этого нужно выполнять клонирование:

$dict = @{'Key'='Value'}
foreach($el in $dict.Keys.Clone()){$dict[$el]='Value4'}
$dict.Keys.Clone() | ForEach-Object {$dict[$PSItem]='Value5'}

Аналогичный вариант с новой переменной:

foreach($key in $($dict.keys)){$dict[$key] = 'Value2'}
$($dict.Keys) | ForEach-Object {$dict[$PSItem] = 'Value3'}

Изменение данных powershell hashtable

 

Hashtable как свойства

Чаще всего в Powershell бы работаем со свойствами и с коллекциями мы можем работать так же.

Создание и получение свойств

Посмотрим как вызываются свойства обычных объектов в PS:

$result = Get-Process
$result.Name

Получение данных powershell hashtable

Аналогично этому примеру мы можем получать из коллекций:

$result = @{'FirstName'='Dimitriy';'Password'='12345'}
$result.FirstName

Получение данных powershell hashtable

Для создания новых свойств можно использовать такой же подход:

# Изменение свойства
$result.FirstName = 'Lena'
# Добавление нового параметра
$result.SubName = 'Polunina'

Изменение данных powershell hashtable

Проверка на существование

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

$collection = @{'FirstName'='Dimitriy';'Password'='12345'}
if($collection.FirstName){
Write-Output ('Имя "{0}" существует' -f $collection.FirstName)
}

проверка powershell hashtable

Я предпочитаю вызывать все ключи и проверять, что в них есть нужный:

if('FirstName' -in $collection.Keys){Write-Output ('Имя "{0}" существует' -f $collection.FirstName)}

Так же есть метод, который проверяет, что в коллекции есть нужный ключ:

if( $collection.ContainsKey('FirstName') ){Write-Output ('Ключ "{0}" существует' -f $collection.FirstName)}

Такой же ключ есть для проверки значений:

if( $collection.ContainsValue('Dimitriy') ){Write-Output ('Имя "{0}" существует' -f $collection.FirstName)}

проверка хэш таблицы powershell

Удаление ключей и значений

Для удаления ключей нужно использовать функцию .Remove():

$collection.remove('Password')

Значение удалить нельзя, так как коллекции существуют только в паре. Можно обнулить подставив $null:

$collection.Name = $null

удаление в хэш таблице powershell

Удалить все элементы коллекции можно переназначив ее:

$coll = @{'Key'='Val';}
$col1 = @{}

Или использовать функцию clear():

$coll = @{'Key'='Val';}
$col1.clear()

удаление хэш таблиц powershell

 

Другие возможности

Кроме хранения значений используя возможности Powershell можно использовать методы .Net и выражения. Все это мы рассмотрим ниже на примерах.

Сортировка

По умолчанию hashtable не отсортированы. Это не очень важно, так как по умолчанию значения мы получаем по ключу, но это можно сделать так:

$coll =[ordered]@{'D'='Val';'B'='Val1';'G'='Val2'}

сортировка хэш таблиц powershell

Выражения

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

Get-WmiObject -Class Win32_logicalDisk | ft -Property DeviceID, `
@{Label="Свободное место(GB)"; Expression={$_.FreeSpace/1Gb}}, `
@{Label="Место на диске всего(GB)"; Expression={$_.Size/1Gb}}

Как вы можете видеть выражение, которые переводит KB в GB, имеет вид хэш таблиц. Мы их можем использовать и другим способом:

$free_space = @{Label="Свободное место(GB)"; Expression={$_.FreeSpace/1Gb}}
$used_space = @{Label="Место на диске всего(GB)"; Expression={$_.Size/1Gb}}
Get-WmiObject -Class Win32_logicalDisk | ft -Property DeviceID, `
$free_space, `
$used_space

выражение в хэш таблице powershell

Label - это имя колонки, а знак $_ элемент, который передается через конвейер.

Выражения можно использовать в командах, которые позволяют это делать. Это команды типа Select-Object и Format-Table

Дополнительные возможности с сортировкой

С командлетом сортировки тоже можно использовать выражения. Связи с этим по этим значениям можно и сортировать результат:

function Get-Sales($val){
$users = @{'Misha':100}
return $users.$val
}
Get-ADUser | Sort-Object @{ expression={ Get-Sales $_.Name } }

Пример выше сортирует пользователей AD по результату из функции.

Сортировка коллекций в массиве

В статье по работе с массивами в Powershell мы рассказывали, что в них могут храниться объекты. Если там хранятся коллекции, то их тоже можно сортировать:

$nums = @(
@{num=2};
@{num=1};
@{num=4};
@{num=5};
@{num=10};
)
$nums | Sort-Object -Property @{expression={$_.num}}

сортировка hashtable powershell

Передача как параметров

Особенностью работы с хеш таблицами в Powershell это возможность их передачи через командную строку. На примере создания папки это выглядит так:

New-Item -Path C:\ -Name 'FolderName' -ItemType Directory -Force

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

$values = @{
Path='C:\';
Name='FolderName1'
ItemType='Directory'
Force=$True
}
New-Item @values

передача как параметров hashtable powershell

Таким образом мы можем передавать сразу несколько таблиц:

$values = @{
Path='C:\';
Name='FolderName2'
}
$typ = @{
ItemType='Directory'
Force=$True
}
New-Item @values @typ

В командах и программах, где значения используются в виде /parametr:value тоже можно использовать такой подход. Для примера Robocopy:

$r = @{R=1;W=1;MT=8}
robocopy source destination @r

Вложенные коллекции

Hashtable и массивы можно вкладывать:

$computers = @{
Computer = 'AD1'
Roles = ('Active Directory','WSUS')
Users = @{'Support'='Alex';'Admin'='Dima'}
}

Вызывать элементы можно так:

$computers.Users.Support

Добавление ключей и значений делается выше описанным способом:

$computers.Users['Disabled'] = 'Obama'

вложенный hashtable powershell

Как видно по скриншоту ключи во вложенных хеш таблицах отображаются как Value. Если вы не знаете какой тип хранит таблица, то сможете узнать это через GetType():

foreach($key in $computers.Keys){$computers[$key].GetType()}

вложенные хэш таблицы powershell

С этим же примером, если мы не будем использовать GetType(), у нас выведутся все элементы:

foreach($key in $computers.Keys){$computers[$key]}

вложенные хэш таблицы powershell

Либо вызывать ключи обращаясь к конкретной коллекции:

foreach($key in $computers.Users.Keys){$computers.Users[$key]}

Предположим, что нам нужно добавить значение в Support. На данный момент это строка, но мы сделаем массив:

# Получение старого
$oldval = $computers.Users.Support
# Добавление старого и нового элемента в массив
$newval = @($oldval,'NewName')
# Добавление массива
$computers.Users.Support = $newval

изменение вложенных хэш таблиц powershell

Преобразование в JSON

Как вы уже заметили хэш таблицы в Powershell сильно похожи на JSON. Если вам удобнее для хранения или использования этот формат, то вы сможете выполнить преобразование:

$dictionary = @{
'response'=@{'token'='12345'};
'time'=@('11-02-2019','11-03-2020')}
$dictionary | ConvertTo-Json

powershell hashtable json

О том как выполнить обратное преобразование будет рассказано ниже.

Вывод hashtable как таблицы

Если вам нужно изменить вывод таким образом, что бы вместо имен колонок были ключи, то это можно сделать через PSCustomObject :

$dictionary = [pscustomobject]@{
'log_name'='Error';
'date'='11-02-2019';
'user'='Administrator';
}
$dictionary

powershell hashtable преобразование

Так же сработает и после создания коллекции:

[pscustomobject]$dictionary

 Можно использовать Out-GridView для представления в графическом интерфейсе:

[pscustomobject]$dictionary | Out-GridView

powershell hashtable преобразование

Если будет интересно могу описать способ добавления hashtable в базу SQL с помощью Powershell и их вывод.

Преобразование в CSV

Подробно о том как работать с CSV в Powershell мы уже говорили. Это одна из самых простых в использовании команд:

$dictionary | ConvertTo-Csv

powershell hashtable преобразование в CSV

Сохранение в файл и загрузка

Мы можем сохранить коллекции в файл для дальнейшего использования. Я не нашел способов с помощью которых можно сохранить коллекции без преобразования. Один из способов использовать импорт и экспорт в формат xml:

$hash = @{'key'='val';'key1'=@{'key1'='val1';'key2'='val2'}}
Export-Clixml -Path "C:\hash.cli" -InputObject $hash

Для загрузки выполним импорт:

Import-Clixml -Path "C:\hash.cli"

powershell hashtable импорт и сохранение

Сохранить в CSV для последующей загрузки возможна, на мой взгляд, только с "костылями". 

Можно использовать JSON, но так получится другой тип данных:

$hash = @{'Path'='C:\*';'Include'='*.json'}
$hash | ConvertTo-JSON | Set-Content -Path "C:\hash.json"

Для загрузки:

$data = Get-Content -Path "C:\hash.json" -Raw | ConvertFrom-JSON

powershell hashtable сохранение и загрузкаКак видно, в случае с JSON, мы больше не можем использовать элемент как параметр командной строки.

Если в файле находится коллекция исходного формата, то вы можете ее загрузить используя такие команды:

$content = Get-Content -Path "C:\hashfile.ps1" -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create( $content )
$hashfile = ( & $scriptBlock ) 

powershell hashtable сохранение в файл

 

Пример c созданием пользователей в AD

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

function Get-UserData($userID){
# Название домена
$OU = "OU=Moscow,DC=domain,DC=local"
$Domain = "domain.local"
$userName = "Test $userID"
$params = @{
   AccountPassword = (ConvertTo-SecureString "Pa$$word123!" -AsPlainText -Force);
   City="Moscow";
   Company="FixMyPC";
   Country="RU";
   DisplayName="Test User ($userID)";
   EmailAddress="$userName@$Domain";
   EmployeeNumber="$userID";
   Enabled=$true;
   GivenName="Test";
   HomePhone="12345-$userID";
   Initials="TU$userID";
   Name="Test User ($userID)";
   Path=$OU;
   PostalCode=$userID;
   SamAccountName=$userName;
   Surname="User ($userID)";
   Title="Title";
   UserPrincipalName="$userName@$Domain";
 }
 return $params
}

1..100 | ForEach-Object {
$data = Get-UserData $PSitem
New-ADUser @data}

 

...

Теги: #powershell


Популярные тэги
О блоге
Этот блог представляет собой конспекты выученного материала, преобретенного опыта и лучшие практики в системном администрировании и программировании.