Хеш таблицы в Powershell или Hashtable это отдельный тип данных, который позволяет удобно хранить значения. Хэш таблицы похожи на словари в Python или формат JSON. Кроме всего этого мы можем передавать этот тип в виде параметров команд. Мы разберем каждую особенность работы с ними на примерах.
Этот тип так же называется коллекциями и ассоциативным массивом. Такой тип данных является более гибким инструментом, чем обычные массивы. Они могут дополнять друг друга при работе в консоли.
Основы по работе с хэш таблицами
Перед тем как перейти к описанию таблиц посмотрим как выглядит другой тип, который был подробно описан в предыдущей статье массивы и листы в Powershell.
Что такое массив
В основном этот тип используется когда мы получаем однородные данные, например листы или числа. Иногда в нем хранится разный тип данных в случаях, когда нельзя предположить какой будет вывод:
$array = @('число', 1, 'строка' )
Вывод этих данных построчный:
$array
Чаще всего они передаются через foreach или вызываются по индексам:
$computers = @('AD1','AD2')
# Вызов по индексу
$computers[0]
# В цикле
foreach($computer in $computers)
{
ping $computer
}
В массивах нельзя удалять и добавлять элементы, но можно делать обновление элементов, и сложение словарей (считайте добавление):
$array = @(1,2,3)
$array[0] = 'Заменили единицу'
$array[1] = ''
$array += 'Новое число'
Что такое hashtable
Такой тип данных как хэш таблицы вы встретите в любом языке программирования. Если в массивах мы используем только значение, то в хеш таблицах мы используем пару ключ и значение.
Пустой hashtasble объявляется так:
$hashtable = @{}
Обратите внимание на различие скобок с массивами.
На следующем примере мы добавим элемент в хэш таблицу выше:
$hashtable = @{}
# Name
$computer = 'Computer'
# Value
$name = 'AD1'
# Добавление в hashtable
$hashtable.add($computer,$name)
# Вызов
$hashtable
В следующих примерах мы научимся получать элементы.
Powershell создание файлов и директорий
Получение и добавление элементов
В отличие от массивов, где мы получали элементов по индексам в hashtable это делается по ключам:
$hashtable['Computers']
В предыдущих примерах мы добавляли параметры через add, но мы можем использовать и другой синтаксис:
# Ключ
$user = 'User'
# Значение
$fname = 'Masha'
# Добавление
$hashtable[$user] = $fname
$hashtable
Можно сложить две коллекции, но при условии что ключи не совпадают так как они должны быть уникальными:
$foo = @{'Street'='Leninskiy Prospekt'}
$foo += @{Zip = '78701'}
Создание хэш таблиц со значениями
Еще одно небольшое отличие от того, что в других языках называется словарями, использования знака = между ключом и значением. Так мы создадим заполненную хеш таблицу:
$user = @{
'User' = 'Alex';
'Computer' = 'CL1';
'Enabled' = $True;
}
Использование точки с запятой не обязательно когда вы пишете на следующей строке, но рекомендуется.
Использование как справочной информации
Хэш таблицы удобно использовать как справочник. Пример ниже позволит узнать на каком сервере вы находитесь сейчас. Вместо значений можно вставить пути до скриптов, которые будут выполнять тестирование:
$hosts = @{
'AD1'='Домен контроллер';
'SQ1'='База данных 1C по складу';
(hostname)='Отсюда начнется скайнет';
'PL1'='Не трогать пока работает';
}
$current_server = $hosts[$env:COMPUTERNAME]
$current_server
$env:COMPUTERNAME вернет имя компьютера из окружения и будет использоваться как ключ для получения данных из хэш таблицы.
Вызов по нескольким ключам
Powershell позволяет вызывать несколько ключей одновременно:
$hosts[@('AD1','SQ1')]
$hosts[('AD1','SQ1')]
$hosts['AD1','SQ1']
Если мы присвоим такие значения переменной, то она будет хранить массив:
Итерации, конвейеры и циклы
Связи с тем, что hashtable отличаются от массивов наличием ключей итерации делаются иначе.
Для примера, если мы передадим хэш таблицу через конвейер, то у нас отобразится количество объектов:
$hsh = @{'key'='val';'key2'='val'}
$hsh | Measure-Object
А если используем счетчик Count отобразится количество значений:
$hsh.count
Мы можем вызвать отдельно ключи:
$hsh.Keys
И отдельно значения:
$hsh.Values
Можно передавать эти значения сразу либо вызывать из конвейера. Для примера у меня есть таблица с возрастом и я могу вызвать эти элементы несколькими способами через конвейер:
$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
Переменная $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
}
Мы редко работаем с хеш таблицами, где не знаем ключей. Если такая ситуация происходит, то мы обращаемся к циклам, которые берут каждый ключ и вызываем по нему значение. Далее будет описан способ как вызвать оба элемента сразу.
GetEnumerator()
Я не помню ситуаций, когда в таких типах данных как ассоциативный массив мне приходилось бы получать ключ и значение сразу, но и такая возможность есть:
$data.GetEnumerator() | ForEach-Object {
$message = 'Key {0} Value {1}' -f $_.key, $_.value
Write-Output $message
}
Изменение используя циклы
В отличие от других языков мы не можем изменять коллекции 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'}
Hashtable как свойства
Чаще всего в Powershell бы работаем со свойствами и с коллекциями мы можем работать так же.
Создание и получение свойств
Посмотрим как вызываются свойства обычных объектов в PS:
$result = Get-Process
$result.Name
Аналогично этому примеру мы можем получать из коллекций:
$result = @{'FirstName'='Dimitriy';'Password'='12345'}
$result.FirstName
Для создания новых свойств можно использовать такой же подход:
# Изменение свойства
$result.FirstName = 'Lena'
# Добавление нового параметра
$result.SubName = 'Polunina'
Проверка на существование
Когда мы работаем с таким типом как hashtable важно убедиться, что ключ по которому мы обращаемся действительно существует. В большинстве языков это делается так:
$collection = @{'FirstName'='Dimitriy';'Password'='12345'}
if($collection.FirstName){
Write-Output ('Имя "{0}" существует' -f $collection.FirstName)
}
Я предпочитаю вызывать все ключи и проверять, что в них есть нужный:
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)}
Удаление ключей и значений
Для удаления ключей нужно использовать функцию .Remove():
$collection.remove('Password')
Значение удалить нельзя, так как коллекции существуют только в паре. Можно обнулить подставив $null:
$collection.Name = $null
Удалить все элементы коллекции можно переназначив ее:
$coll = @{'Key'='Val';}
$col1 = @{}
Или использовать функцию clear():
$coll = @{'Key'='Val';}
$col1.clear()
Другие возможности
Кроме хранения значений используя возможности Powershell можно использовать методы .Net и выражения. Все это мы рассмотрим ниже на примерах.
Сортировка
По умолчанию hashtable не отсортированы. Это не очень важно, так как по умолчанию значения мы получаем по ключу, но это можно сделать так:
$coll =[ordered]@{'D'='Val';'B'='Val1';'G'='Val2'}
Выражения
В одной из предыдущих статей мы вычисляли свободное место на диске с помощью 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
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}}
Передача как параметров
Особенностью работы с хеш таблицами в Powershell это возможность их передачи через командную строку. На примере создания папки это выглядит так:
New-Item -Path C:\ -Name 'FolderName' -ItemType Directory -Force
Иногда команды бывают очень длинными, что затрудняет чтение в скриптах. Мы можем изменить это представление передав параметры через коллекцию:
$values = @{
Path='C:\';
Name='FolderName1'
ItemType='Directory'
Force=$True
}
New-Item @values
Таким образом мы можем передавать сразу несколько таблиц:
$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'
Как видно по скриншоту ключи во вложенных хеш таблицах отображаются как Value. Если вы не знаете какой тип хранит таблица, то сможете узнать это через GetType():
foreach($key in $computers.Keys){$computers[$key].GetType()}
С этим же примером, если мы не будем использовать GetType(), у нас выведутся все элементы:
foreach($key in $computers.Keys){$computers[$key]}
Либо вызывать ключи обращаясь к конкретной коллекции:
foreach($key in $computers.Users.Keys){$computers.Users[$key]}
Предположим, что нам нужно добавить значение в Support. На данный момент это строка, но мы сделаем массив:
# Получение старого
$oldval = $computers.Users.Support
# Добавление старого и нового элемента в массив
$newval = @($oldval,'NewName')
# Добавление массива
$computers.Users.Support = $newval
Преобразование в JSON
Как вы уже заметили хэш таблицы в Powershell сильно похожи на JSON. Если вам удобнее для хранения или использования этот формат, то вы сможете выполнить преобразование:
$dictionary = @{
'response'=@{'token'='12345'};
'time'=@('11-02-2019','11-03-2020')}
$dictionary | ConvertTo-Json
О том как выполнить обратное преобразование будет рассказано ниже.
Получение списка USB устройств в Powershell
Вывод hashtable как таблицы
Если вам нужно изменить вывод таким образом, что бы вместо имен колонок были ключи, то это можно сделать через PSCustomObject :
$dictionary = [pscustomobject]@{
'log_name'='Error';
'date'='11-02-2019';
'user'='Administrator';
}
$dictionary
Так же сработает и после создания коллекции:
[pscustomobject]$dictionary
Можно использовать Out-GridView для представления в графическом интерфейсе:
[pscustomobject]$dictionary | Out-GridView
Если будет интересно могу описать способ добавления hashtable в базу SQL с помощью Powershell и их вывод.
Преобразование в CSV
Подробно о том как работать с CSV в Powershell мы уже говорили. Это одна из самых простых в использовании команд:
$dictionary | ConvertTo-Csv
Сохранение в файл и загрузка
Мы можем сохранить коллекции в файл для дальнейшего использования. Я не нашел способов с помощью которых можно сохранить коллекции без преобразования. Один из способов использовать импорт и экспорт в формат xml:
$hash = @{'key'='val';'key1'=@{'key1'='val1';'key2'='val2'}}
Export-Clixml -Path "C:\hash.cli" -InputObject $hash
Для загрузки выполним импорт:
Import-Clixml -Path "C:\hash.cli"
Сохранить в 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
Как видно, в случае с JSON, мы больше не можем использовать элемент как параметр командной строки.
Если в файле находится коллекция исходного формата, то вы можете ее загрузить используя такие команды:
$content = Get-Content -Path "C:\hashfile.ps1" -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create( $content )
$hashfile = ( & $scriptBlock )
Пример 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}
...
Подписывайтесь на наш Telegram канал
Теги: #powershell