Buscando PowerShell con CIM
Ayer compartí un script que puede usar para inventariar sistemas para instalaciones de Windows PowerShell y PowerShell 7. Esto debería funcionar para la mayoría de las personas que instalan PowerShell 7 con el instalador proporcionado. Pero, como me han señalado más de una vez, esto no detectará ninguna instalación con carga lateral o fuera de banda. Hice referencia a esto en el artículo anterior. Creo que lo mejor que puedes hacer es buscar en las instancias del disco duro de pwsh.exe. Pero no queremos hacer una búsqueda de directorio recursiva por fuerza bruta. En lugar de eso, usaré Get-CimInstance.
Get-CimInstance -ClassName CIM_Logicalfile -Filter "filename='pwsh' AND extension = 'exe' AND drive='C:'"Estoy limitando la búsqueda al controlador C para acelerar las cosas. Este tipo de búsqueda encuentra instalaciones estables y de vista previa.
Esto me da la misma información que tenía antes y un poco más. Todavía puedo buscar Windows PowerShell de la misma manera usando Get-Command.
La secuencia de comandos
Aquí hay un guión revisado.
#requires -version 5.1
[cmdletbinding()]
Param(
[Parameter(HelpMessage = "Specify the drive to search for instances of pwsh.exe")]
[ValidatePattern("^[c-zC-Z]$")]
[string]$Drive = "C"
)
#a private function to shorten filenames
Function _shortName {
Param($path)
$parts = $path.split("\")
$out = foreach ($part in $parts) {
if ($part -match "\s") {
"{0}~1" -f $part.Substring(0, 6)
}
else {
$part
}
}
$out -join "\"
}
Write-Verbose "Searching for PowerShell installations on $([System.Environment]::MachineName)"
$os = (Get-CimInstance -ClassName Win32_OperatingSystem -Property Caption).caption
#region Windows Powershell
Try {
Write-Verbose "Testing for Windows PowerShell"
$cmd = Get-Command -Name powershell.exe -ErrorAction stop
if ($cmd) {
Write-Verbose "Using $($cmd.path)"
#need to run Powershell.exe to get $PSVersion
$psh = &$cmd.path -nologo -noprofile -command { Get-Host }
#test for PowerShell 2.0 engine or feature
Write-Verbose "Testing for Windows PowerShell 2.0 engine or feature"
#get computersystem roles to determine if running on a server or client
#assuming operating system caption uses 'Server'
if ($os.caption -match "server") {
Write-Verbose "Running Get-WindowsFeature"
$f = Get-WindowsFeature PowerShell-V2
}
else {
Write-Verbose "Running Get-WindowsOptionalFeature"
$f = Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root
}
if ($f.installed -OR $f.State -eq 'Enabled') {
$comment = "Windows PowerShell 2.0 feature enabled or installed"
}
else {
$comment = ""
}
#the install date will reflect the last time the OS was updated or installed
[pscustomobject]@{
PSTypeName = "PSInstallInfo"
Name = $cmd.Name
Path = $cmd.source
PSVersion = $psh.Version
Installed = (Get-Item $cmd.source).CreationTime
Comments = $comment
Computername = [System.Environment]::MachineName
OperatingSystem = $os
}
Remove-Variable cmd
} #$cmd found
} #try
Catch {
#this should never happen
Write-Verbose "Windows PowerShell not installed on $([Environment]::MachineName)."
}
#endregion
#region PowerShell 7
Write-Verbose "Testing for PowerShell 7 including preview builds"
$pwsh = Get-CimInstance -ClassName CIM_Logicalfile -Filter "filename='pwsh' AND extension = 'exe' AND drive='$($drive):'"
if ($pwsh) {
Foreach ($item in $pwsh) {
if ($item.path -match 'preview') {
$comment = "Preview"
}
else {
$comment = ""
}
#test for SSH
if (Test-Path $env:programdata\ssh\sshd_config ) {
#get short name
$short = _shortName $item.EightDotThreeFileName
Write-Verbose "Testing for an SSH subsystem using $short"
$ssh = Get-Content $env:programdata\ssh\sshd_config | Select-String $($short -replace "\\", "\\")
#'(?<=powershell\s).*pwsh.exe'
if ($ssh.matches.value -eq $short) {
$comment += " SSH configured"
}
}
[pscustomobject]@{
PSTypeName = "PSInstallInfo"
Name = Split-Path $item.name -Leaf
Path = Split-Path $item.name
PSVersion = $item.version
Installed = $item.InstallDate
Comments = $comment.Trim()
Computername = [System.Environment]::MachineName
OperatingSystem = $os
}
}
}
#endregion
#end of script
Cambios
Hice algunos cambios en esta versión. Primero, decidí no tratar el motor PowerShell 2.0 como una versión separada. Utiliza la misma versión de powershell.exe tanto para 5.1 como para 2.0. En cambio, reflejo el estado del motor 2.0 en el comentario. También adopté un enfoque diferente para SSH. En lugar de probar si SSH está instalado y ejecutándose, estoy revisando el archivo sshd_config para detectar la existencia de un subsistema PowerShell. Esto es lo que le permite conectarse mediante comunicación remota SSH en PowerShell.
Esto requiere un poco de manipulación porque el nombre de archivo largo de Windows está truncado en el archivo sshd_config. Entonces, en lugar de buscar C:\Program Files\PowerShell\7\pwsh.exe, estoy buscando "c:\progra~1\powershell\7\pwsh.exe". Escribí una breve función auxiliar para convertir la ruta. Me doy cuenta de que esto no es infalible pero funciona para mis instalaciones SSH.
Aquí está la salida local predeterminada.
Como antes, puede utilizar la comunicación remota para inventariar otros servidores y escritorios.
$c = Invoke-Command -FilePath C:\scripts\Get-PSExeFile.ps1 -ComputerName win10,srv1,srv2 -HideComputerName | sort computername¿Te gusta eso? Revisé el archivo de formato ps1xml.
<!--
Format type data generated 07/13/2021 12:14:45 by PROSPERO\Jeff
This file was created using the New-PSFormatXML command that is part
of the PSScriptTools module.
https://github.com/jdhitsolutions/PSScriptTools
-->
<Configuration>
<ViewDefinitions>
<View>
<!--Created 07/13/2021 12:14:45 by PROSPERO\Jeff-->
<Name>default</Name>
<ViewSelectedBy>
<TypeName>PSInstallInfo</TypeName>
</ViewSelectedBy>
<GroupBy>
<ScriptBlock>
if ($host.name -match "console|code|serverremotehost") {
"{0} [$([char]27)[3m{1}$([char]27)[0m]" -f $_.Computername,$_.OperatingSystem
}
else {
"{0} [{1}]" -f $_.Computername,$_.OperatingSystem
}
</ScriptBlock>
<Label>Computername</Label>
</GroupBy>
<TableControl>
<!--Delete the AutoSize node if you want to use the defined widths
<AutoSize />.-->
<TableHeaders>
<TableColumnHeader>
<Label>Name</Label>
<Width>19</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>PSVersion</Label>
<Width>16</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Installed</Label>
<Width>12</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Comments</Label>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>
if (($host.name -match "console|code|serverremotehost") -AND ($_.path -match "preview")) {
"$([char]27)[38;5;219m$($_.PSVersion)$([char]27)[0m"
}
else {
$_.PSVersion
}
</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>$_.Installed.ToShortDateString()</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>
if (($host.name -match "console|code|serverremotehost") -AND ($_.comments-match "SSH configured")) {
<!-- replace SSH Detected with an ANSI sequence-->
$_.comments -replace "SSH configured","$([char]27)[1;38;5;155mSSH configured$([char]27)[0m"
}
else {
$_.comments
}
</ScriptBlock>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>Resumen
En este punto, deberías tener varias opciones. Esperemos que alguno de ellos satisfaga sus necesidades. O siéntete libre de cortar y pegar las piezas que quieras en tu propia herramienta. Si adopta este enfoque, espero que comparta su trabajo. Preguntas y comentarios siempre bienvenidos. Disfrutar.


