【Powershell】構成管理の自動化

## 【Powershell】構成管理の自動化

### この記事でわかる・できること

- LAN内の構成情報を自動取得

### この記事の対象者

- 構成管理の自動化をしたいが有償のツールは使いたくない人

### 動作環境・使用するツールや言語

- Windows Server 2019 Standard
- Powershell 5.1.17763.6054

### はじめに

構成管理では社内のハードウェア・ネットワーク・ソフトウェア・ライセンス等が対象になります。Excel等で手動管理するか管理ツールを導入するかというところですが、今回はPowershellで取得できるものを取得してみます。
対象はActive Directory（AD）、Windows PCのディスク・コンピュータ・インストール済のソフトウェア、有効なIPアドレスの一覧です。AD以外は対象がオンラインでないと取得できないのでご注意ください。

### Active Directory
AD上で実行します。

まずADの各情報をcsvに出力するpowershellを作成します。csvはとりあえず上書きしています。

```powershell:
$file_path = 【格納フォルダ】;

# コンピュータ
Get-ADComputer -Filter * -Property * |
Select-Object Name,ObjectClass,Enabled,lastLogonDate,IPv4Address,OperatingSystem,OperatingSystemServicePack,OperatingSystemVersion |
Export-CSV $file_path\adcomputer.csv -NoTypeInformation -Encoding UTF8

# グループ
Get-ADGroup -Filter * |
select @{Label = "GroupName"; Expression = {$_.Name}},ObjectClass,@{Label = "MemberNames"; Expression = {($_| Get-ADGroupMember| select -ExpandProperty Name) -join ","}}|
export-csv $file_path\adgroup.csv -NoTypeInformation -Encoding UTF8 

# ユーザ
Get-ADUser -Filter * -Properties * |
select Name,ObjectClass,Enabled |
export-csv $file_path\aduser.csv -NoTypeInformation -Encoding UTF8 

```
BATでPowershellを実行するようにします。
任意ですが、ついでに出力したcsvをローカルのmysqlにインポートします。
local_infileはサーバ側とクライアント側の両方で有効にしておく必要がありますが、デフォルトでは無効なのでご注意ください。
パスワードが平文だと良くないのでmysql_config_editorでパスワードを暗号化します。これは一度だけでいいです。
```powershell:
mysql_config_editor set -G local -h localhost -u root -p
【パスワードを聞かれるので入力】
```

```bat
powershell -ExecutionPolicy RemoteSigned -File 【powershellのパス】
# mysql_config_editorのset -Gで設定したlocalをlogin-pathに設定する
mysql --login-path=local -D 【データベース名】 --local_infile=on< 【インポート用のSQLファイル】
```
インポート用のSQLを作成しておきます。テーブルは別途事前に作成しておいてください。
こちらも上書きです。
```sql:
TRUNCATE TABLE ADUSER;
TRUNCATE TABLE ADGROUP;
TRUNCATE TABLE ADCOMPUTER;

LOAD DATA LOCAL INFILE '【格納フォルダ】/aduser.csv' INTO TABLE ADUSER
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
IGNORE 1 ROWS;

LOAD DATA LOCAL INFILE '【格納フォルダ】/adgroup.csv' INTO TABLE ADGROUP
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
IGNORE 1 ROWS;

LOAD DATA LOCAL INFILE '【格納フォルダ】/adcomputer.csv' INTO TABLE ADCOMPUTER
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
IGNORE 1 ROWS;
```
あとはBATをタスクスケジューラに登録すれば自動的にcsv出力とインポートができます。


### IPアドレス一覧
Get-NetNeighborでIPアドレスの一覧を取得し、名前解決できるものはGetHostEntryで名前解決します。
```powershell:
$ips = Get-NetNeighbor -State Stale,Permanent,Reachable -AddressFamily IPv4
$folder_path = 【格納フォルダ】
$today = (Get-Date).ToString('yyyy/MM/dd')

if(Test-Path $folder_path\ip_list.csv){
	Remove-Item $folder_path\ip_list.csv
}

$Collection =New-Object System.Collections.ArrayList

ForEach($ip in $ips){
	$Property = New-Object -TypeName PSObject

	try{
	    $dnsinfo = [System.Net.Dns]::GetHostEntry($ip.IPAddress)

	    $Property |Add-Member -Type NoteProperty -Name "today" -Value $today
	    $Property |Add-Member -Type NoteProperty -Name "IPAddress" -Value $ip.IPAddress
	    $Property |Add-Member -Type NoteProperty -Name "State" -Value $ip.State
	    $Property |Add-Member -Type NoteProperty -Name "LinkLayerAddress" -Value $ip.LinkLayerAddress
	    $Property |Add-Member -Type NoteProperty -Name "HostName" -Value $dnsinfo.HostName
	    $Collection.add($Property)
	}catch{
	    $Property |Add-Member -Type NoteProperty -Name "today" -Value $today
	    $Property |Add-Member -Type NoteProperty -Name "IPAddress" -Value $ip.IPAddress
	    $Property |Add-Member -Type NoteProperty -Name "State" -Value $ip.State
	    $Property |Add-Member -Type NoteProperty -Name "LinkLayerAddress" -Value $ip.LinkLayerAddress
	    $Property |Add-Member -Type NoteProperty -Name "HostName" -Value $null
	    $Collection.add($Property)
	}
}

$Collection |
Export-CSV -Append $folder_path\ip_list.csv -NoTypeInformation -Encoding UTF8
```
batとSQLは同様です。
```bat
powershell -ExecutionPolicy RemoteSigned -File 【powershellのパス】

mysql --login-path=local -D cw --local_infile=on< 【インポート用のSQLファイル】
```
```sql:
TRUNCATE TABLE IP_LIST;

LOAD DATA LOCAL INFILE '【格納フォルダ】/ip_list.csv' INTO TABLE IP_LIST
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
IGNORE 1 ROWS;
;
```

### Windows情報
#### ローカルで実行する場合
Get-WmiObjectで情報を取得しますが、リモートのPCに対して実行するとRPCのエラーになる場合があります。ファイアウォールを開けてもいいですが、セキュリティの都合上開けられない場合はローカルで実行してファイルを共有フォルダに格納するのがよいでしょう。
```powershell:
$target = hostname
$folder_path = 【格納フォルダ】

if(Test-Path $folder_path\software_list_$target.csv){
	Remove-Item $folder_path\software_list_$target.csv
}
if(Test-Path $folder_path\computer_list_$target.csv){
	Remove-Item $folder_path\computer_list_$target.csv
}
if(Test-Path $folder_path\disk_list_$target.csv){
	Remove-Item $folder_path\disk_list_$target.csv
}

#ソフトウェア情報を取得

Get-WmiObject -Class Win32_Product -ComputerName $target | 
Select-Object @{l="Acquision_Date";e={(Get-Date).ToString('yyyy/MM/dd')}},@{l="PC_Name";e={$target}},Name,Version,Vendor |
Export-CSV -Append $folder_path\software_list_$target.csv -NoTypeInformation -Encoding UTF8

#コンピュータ情報を取得
Get-WmiObject -Class Win32_ComputerSystem -ComputerName $target | 
Select-Object @{l="Acquision_Date";e={(Get-Date).ToString('yyyy/MM/dd')}},Name,DNSHostName,Manufacturer,Model,NumberOfLogicalProcessors,NumberOfProcessors,PartOfDomain,Domain,`
	@{l="TotalPhysicalMemory(GB)";e={[Math]::round(($_.TotalPhysicalMemory)/1024/1024/1024)}}|
Export-CSV -Append $folder_path\computer_list_$target.csv -NoTypeInformation -Encoding UTF8

#ディスク情報を取得
Get-WmiObject -Class Win32_LogicalDisk -ComputerName $target | 
Select-Object @{l="Acquision_Date";e={(Get-Date).ToString('yyyy/MM/dd')}},@{l="PC_Name";e={$target}},`
@{l="DriveType";e={`
switch($_.DriveType){
    0 {"Unknown"}
    1 {"NoRootDirectory"}
    2 {"Removable"}
    3 {"Fixed"}
    4 {"Network"}
    5 {"CDRom"}
    default {"Not matched."}
}
}},`
DeviceID,Description,FileSystem,@{l="FreeSpace(GB)";e={[Math]::round($_.FreeSpace/1024/1024/1024)}},
@{l="Size(GB)";e={[Math]::round($_.Size/1024/1024/1024)}},
@{l="Usage(%)";e={[Math]::round(($_.Size - $_.FreeSpace)/$_.Size*100,1)}}|
Export-CSV -Append $folder_path\disk_list_$target.csv -NoTypeInformation -Encoding UTF8
```
他と同様にバッチを作成してタスクスケジューラに登録します。
ローカルではここまでです。
```bat:
powershell -ExecutionPolicy RemoteSigned -File 【powershellのパス】
```

#### リモート実行する場合
リモートのPCに対して実行する場合はWMI関連のファイアウォールを事前に開けておきます。
取得対象のPC上で下記を管理者として実行します。
なおファイアウォールを開けても、RPCのサービスが実行中になっていなかったりレジストリの値が足りないなど様々な理由でエラーが発生する可能性があります。
```powershell:
netsh advfirewall firewall set rule group="Windows Management Instrumentation (WMI)" new enable=yes
```
##### ドメイン管理下にある場合
ドメイン管理下にあるものはドメインの管理者であれば実行できると思います。
```powershell:
$targets = @(【対象PCリスト】
$folder_path = 【格納フォルダ】

if(Test-Path $folder_path\software_list.csv){
	Remove-Item $folder_path\software_list.csv
}
if(Test-Path $folder_path\computer_list.csv){
	Remove-Item $folder_path\computer_list.csv
}
if(Test-Path $folder_path\disk_list.csv){
	Remove-Item $folder_path\disk_list.csv
}

#ソフトウェア情報を取得

foreach($target in $targets){
	Get-WmiObject -Class Win32_Product -ComputerName $target | 
	Select-Object @{l="Acquision_Date";e={(Get-Date).ToString('yyyy/MM/dd')}},@{l="PC_Name";e={$target}},Name,Version,Vendor |
	Export-CSV -Append $folder_path\software_list.csv -NoTypeInformation -Encoding UTF8
}

#コンピュータ情報を取得
foreach($target in $targets){
	Get-WmiObject -Class Win32_ComputerSystem -ComputerName $target | 
	Select-Object @{l="Acquision_Date";e={(Get-Date).ToString('yyyy/MM/dd')}},Name,DNSHostName,Manufacturer,Model,NumberOfLogicalProcessors,NumberOfProcessors,PartOfDomain,Domain,`
	 @{l="TotalPhysicalMemory(GB)";e={[Math]::round(($_.TotalPhysicalMemory)/1024/1024/1024)}}|
	Export-CSV -Append $folder_path\computer_list.csv -NoTypeInformation -Encoding UTF8
}

#ディスク情報を取得
foreach($target in $targets){
	Get-WmiObject -Class Win32_LogicalDisk -ComputerName $target | 
	Select-Object @{l="Acquision_Date";e={(Get-Date).ToString('yyyy/MM/dd')}},@{l="PC_Name";e={$target}},`
    @{l="DriveType";e={`
    switch($_.DriveType){
        0 {"Unknown"}
        1 {"NoRootDirectory"}
        2 {"Removable"}
        3 {"Fixed"}
        4 {"Network"}
        5 {"CDRom"}
        default {"Not matched."}
    }
    }},`
    DeviceID,Description,FileSystem,@{l="FreeSpace(GB)";e={[Math]::round($_.FreeSpace/1024/1024/1024)}},
    @{l="Size(GB)";e={[Math]::round($_.Size/1024/1024/1024)}},
    @{l="Usage(%)";e={[Math]::round(($_.Size - $_.FreeSpace)/$_.Size*100,1)}}|
	Export-CSV -Append $folder_path\disk_list.csv -NoTypeInformation -Encoding UTF8
}
```
##### ドメイン管理下にない場合
ドメインの管理下にないものはユーザ名とパスワードの指定が必要なようです。
パスワードは暗号化してテキストファイルとして格納しておきます。
下記を参考にさせていただきました。
https://qiita.com/Nao2011/items/cc771635326f06017470
作成は一度だけでいいです。
```powershell:
$Pass = "【パスワード】"
$Securepass = $Pass | ConvertTo-SecureString -AsPlaintext -force
$encrypt = ConvertFrom-SecureString -SecureString $SecurePass
$encrypt | Out-File 【暗号化後のテキストファイル】
```
Get-WmiObjectの時に-credentialを付加しています。
```powershell:
$encrypt = Get-Content 【暗号化後のテキストファイル】
$pass = ConvertTo-SecureString -String $encrypt
$cre =New-Object System.Management.Automation.PSCredential 【ユーザ名】,$pass

$targets = @(【対象PCリスト】)

#ソフトウェア情報を取得

foreach($target in $targets){
	Get-WmiObject -Class Win32_Product -credential $cre -ComputerName $target | 
	Select-Object @{l="Acquision_Date";e={(Get-Date).ToString('yyyy/MM/dd')}},@{l="PC_Name";e={$target}},Name,Version,Vendor |
	Export-CSV -Append $folder_path\software_list.csv -NoTypeInformation -Encoding UTF8
}

#コンピュータ情報を取得
foreach($target in $targets){
	Get-WmiObject -Class Win32_ComputerSystem -credential $cre -ComputerName $target | 
	Select-Object @{l="Acquision_Date";e={(Get-Date).ToString('yyyy/MM/dd')}},Name,DNSHostName,Manufacturer,Model,NumberOfLogicalProcessors,NumberOfProcessors,PartOfDomain,Domain,`
	 @{l="TotalPhysicalMemory(GB)";e={[Math]::round(($_.TotalPhysicalMemory)/1024/1024/1024)}}|
	Export-CSV -Append $folder_path\computer_list.csv -NoTypeInformation -Encoding UTF8
}

#ディスク情報を取得
foreach($target in $targets){
	Get-WmiObject -Class Win32_LogicalDisk -credential $cre  -ComputerName $target | 
	Select-Object @{l="Acquision_Date";e={(Get-Date).ToString('yyyy/MM/dd')}},@{l="PC_Name";e={$target}},`
    @{l="DriveType";e={`
    switch($_.DriveType){
        0 {"Unknown"}
        1 {"NoRootDirectory"}
        2 {"Removable"}
        3 {"Fixed"}
        4 {"Network"}
        5 {"CDRom"}
        default {"Not matched."}
    }
    }},`
    DeviceID,Description,FileSystem,@{l="FreeSpace(GB)";e={[Math]::round($_.FreeSpace/1024/1024/1024)}},
    @{l="Size(GB)";e={[Math]::round($_.Size/1024/1024/1024)}},
    @{l="Usage(%)";e={[Math]::round(($_.Size - $_.FreeSpace)/$_.Size*100,1)}}|
	Export-CSV -Append $folder_path\disk_list.csv -NoTypeInformation -Encoding UTF8
}
```
他と同様にpowershellを実行し、mysqlにインポートします。
```bat:
powershell -ExecutionPolicy RemoteSigned -File 【powershellのパス】

mysql --login-path=local -D cw --local_infile=on< 【インポート用のSQLファイル】
```
ローカルのcsvはPCごとに分けたのでそれごとにインポートしています。
タスクスケジューラはローカル側のタスクが終わっているタイミングで実行します。
```sql:
TRUNCATE TABLE COMPUTER_LIST;
TRUNCATE TABLE SOFTWARE_LIST;
TRUNCATE TABLE DISK_LIST;

LOAD DATA LOCAL INFILE '【格納フォルダ】/computer_list.csv' INTO TABLE COMPUTER_LIST
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
IGNORE 1 ROWS;

LOAD DATA LOCAL INFILE '【格納フォルダ】/computer_list_【ローカルPC】.csv' INTO TABLE COMPUTER_LIST
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
IGNORE 1 ROWS;

LOAD DATA LOCAL INFILE '【格納フォルダ】/software_list.csv' INTO TABLE SOFTWARE_LIST
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
IGNORE 1 ROWS;

LOAD DATA LOCAL INFILE '【格納フォルダ】/software_list_【ローカルPC】.csv' INTO TABLE SOFTWARE_LIST
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
IGNORE 1 ROWS;

LOAD DATA LOCAL INFILE '【格納フォルダ】/disk_list.csv' INTO TABLE DISK_LIST
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
IGNORE 1 ROWS;

LOAD DATA LOCAL INFILE '【格納フォルダ】/disk_list_【ローカルPC】.csv' INTO TABLE DISK_LIST
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'
IGNORE 1 ROWS;
```