Get the drive letter of USB drive in PowerShell - powershell

I've seen articles in C# and some other languages that explain how to achieve what I'm looking for but I don't know how to convert them.
The following link explains how to get the answer:
How can I get the drive letter of an USB device?
Win32_DiskDrive-> Win32_DiskDriveToDiskPartition -> Win32_DiskPartition -> Win32_LogicalDiskToPartition -> Win32_LogicalDisk
The answer by GEOCHET explains also explains how to achieve the answer but again, not in PowerShell: How to find USB drive letter?

Try:
gwmi win32_diskdrive | ?{$_.interfacetype -eq "USB"} | %{gwmi -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=`"$($_.DeviceID.replace('\','\\'))`"} WHERE AssocClass = Win32_DiskDriveToDiskPartition"} | %{gwmi -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=`"$($_.DeviceID)`"} WHERE AssocClass = Win32_LogicalDiskToPartition"} | %{$_.deviceid}
Tested with one and more than one USB device plugged-in.

I know the subject has been dropped for a while, but since it's something I come back to pretty often, I thought I'd update things a bit.
If using Windows 7 and above, a much simpler solution would be:
Get-WmiObject Win32_Volume -Filter "DriveType='2'"
And if you want to avoid magic numbers:
Get-WmiObject Win32_Volume -Filter ("DriveType={0}" -f [int][System.IO.DriveType]::Removable)
References:
https://learn.microsoft.com/en-us/previous-versions/windows/desktop/vdswmi/win32-volume
https://learn.microsoft.com/en-us/dotnet/api/system.io.drivetype

get-volume | where drivetype -eq removable | foreach driveletter
volume | ? drivetype -eq removable | % driveletter

Beginning with PowerShell v3.0, Microsoft introduce the Get-Cim* commands which make this easier than the ugliness of the Get-WmiObject ASSOCIATORS query method:
Get-CimInstance -Class Win32_DiskDrive -Filter 'InterfaceType = "USB"' -KeyOnly |
Get-CimAssociatedInstance -ResultClassName Win32_DiskPartition -KeyOnly |
Get-CimAssociatedInstance -ResultClassName Win32_LogicalDisk |
Format-List *
Or:
Get-CimInstance -Class Win32_DiskDrive -Filter 'InterfaceType = "USB"' -KeyOnly |
Get-CimAssociatedInstance -Association Win32_DiskDriveToDiskPartition -KeyOnly |
Get-CimAssociatedInstance -Association Win32_LogicalDiskToPartition |
Format-List *
The above commands are equivalent.

$FlashDrives = (get-volume | Where-Object drivetype -eq removable).DriveLetter
foreach($elment in $FlashDrives)
{
if($null -eq $elment)
{
}
else
{
$FlashDriveLetter = $elment + ":\"
Write-Host $FlashDriveLetter
}
}
$HardDiskDrives = (get-volume | Sort-Object -Property DriveLetter | Where-Object drivetype -eq Fixed).DriveLetter
foreach($elment in $HardDiskDrives)
{
if($null -eq $elment)
{
}
else
{
$HardDiskDriveLetter = $elment + ":\"
Write-Host $HardDiskDriveLetter
}
}

Related

Formatting multiple result sets together in powershell

Get-WmiObject -Class Win32_OperatingSystem -ComputerName (Get-Content "C:\Temp\Servers.txt") | SELECT-Object PSComputerName, #{Name="Memory (RAM in GB)";Expression={[Math]::Round($_.TotalVisibleMemorySize/1024/1024)}} | Format-Table
Get-WmiObject -Class Win32_logicaldisk -ComputerName (Get-Content "C:\Temp\Servers.txt") | Select-Object PSComputerName, DriveType, DeviceID, VolumeName, #{Name="Size";Expression={[math]::ceiling($_.Size /1GB)}} , #{Name="FreeSpace";Expression={[math]::ceiling($_.FreeSpace /1GB)}}, Compressed | where DriveType -eq 3 | Format-Table
Get-WmiObject -Class Win32_OperatingSystem -ComputerName (Get-Content "C:\Temp\Servers.txt")| Select-Object PSComputerName, BuildNumber, BuildType, Caption, CodeSet, OSArchitecture, SystemDrive, TotalVisibleMemorySize, Version | Format-Table
Get-WmiObject -Class win32_product -ComputerName (Get-Content "C:\Temp\Servers.txt") | Select-Object Name, Version, Vendor, InstallDate | Format-Table
Get-WmiObject -Class Win32_Service -ComputerName (Get-Content "C:\Temp\Servers.txt") | Select-Object PSComputerName, DisplayName, StartName, PathName, StartMode| where DisplayName -Like "*xyz*" |Format-Table
I have till now managed to piece together the above to get the information I need from serveral servers, however now I want to format it so that I can collate information for each server in a format that I can display
for eg.
Server : ABC
RAM : 64 GB
Number of Processors : 8
Disk :
Table of disk Sizes Etc
Any pointers would be appreciated
With all these properties, you would get a nested object array, which probably is easiest to view in JSON format.
I have changed all Get-WmiObject into the newer and faster Get-CimInstance cmdlets below
$result = Get-Content "C:\Temp\Servers.txt" | ForEach-Object {
# create an ordered hashtable to store the results for each server
$pcinfo = [ordered]#{}
# System info
$data = Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $_
$pcinfo['Computer'] = $data.PSComputerName
$pcinfo['Memory (RAM in GB)'] = '{0:N2}' -f ($data.TotalPhysicalMemory / 1GB)
# OS info
$data = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $_
$pcinfo['BuildNumber'] = $data.BuildNumber
$pcinfo['BuildType'] = $data.BuildType
$pcinfo['Caption'] = $data.Caption
$pcinfo['CodeSet'] = $data.CodeSet
$pcinfo['OSArchitecture'] = $data.OSArchitecture
$pcinfo['SystemDrive'] = $data.SystemDrive
$pcinfo['TotalVisibleMemorySize'] = $data.TotalVisibleMemorySize
$pcinfo['Version'] = $data.Version
# Product info (array of objects)
$pcinfo['Products'] = Get-CimInstance -ClassName Win32_Product -ComputerName $_ |
Select-Object Name, Version, Vendor, InstallDate
# Local fixed disk info (array of objects)
$pcinfo['FixedDrives'] = Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $_ -Filter 'DriveType=3' |
Sort-Object DeviceID |
Select-Object DriveType, DeviceID, VolumeName,
#{Name="Size";Expression={"{0:N2} GB" -f ($_.Size / 1GB)}},
#{Name="FreeSpace";Expression={"{0:N2} GB" -f ($_.FreeSpace / 1GB)}},
Compressed
# Services info (array of objects)
$pcinfo['Services'] = Get-CimInstance -ClassName Win32_Service -ComputerName $_ |
Where-Object { $_.DisplayName -like '*Adobe*' } |
Select-Object DisplayName, StartName, PathName, StartMode
# convert the hashtable to PSObject and output
[PsCustomObject]$pcinfo
}
# output the whole structure as JSON for easier reading and optionally save it to file
$result | ConvertTo-Json -Depth 3 # | Set-Content -Path 'Path\To\Output.json' -Force

Optimal way of creating object from two objects

I built this, which is working fine, it takes about 7-9 seconds to run and display.
I'm wondering, is there a faster/optimal way of building this custom object?
As you can see, I want all the drivers information from Win32_PNPsigneddriver but to that, I add two other properties from Win32_PNPentity (configmanagererrorcode and status) based on the DeviceID.
This way, the final object contains all drivers and shows if there is an error for the device using that driver.
$poste = "COMPUTER1234"
$DriversUp = Get-WmiObject -computername $poste Win32_PNPsigneddriver |
Where-Object {$_.DeviceName -ne $null}
$Devices = Get-WmiObject -computername $poste Win32_PNPentity
$DriversDevices = foreach ($driver in $DriversUp) {
$driver |
  Select-Object DeviceClass, Manufacturer, DeviceName,
FriendlyName, DriverName, InfName,
#{name='Status';expression={$Devices | Where-Object {$_.DeviceID -eq "$($driver.DeviceID)"} | Select-Object -ExpandProperty status}},
#{name='ConfigManagerErrorCode';expression={$Devices | Where-Object {$_.DeviceID -eq "$($driver.DeviceID)"} | Select-Object -ExpandProperty ConfigManagerErrorCode}},
#{name='DriverDate';expression={[DateTime]::ParseExact(($_.DriverDate).Split('.')[0], "yyyyMMddHHmmss", [System.Globalization.CultureInfo]::InvariantCulture)}},
DriverVersion
}
$DriversDevices |
Sort-Object DeviceClass |
 Out-GridView -Title "$poste - Drivers utilisés"
Like I said everything works fine already. However, I'm curious to know if there's a faster way!
Starting in PowerShell 3.0, the Get-WmiObject cmdlet has been superseded by Get-CimInstance.
$poste = "COMPUTER1234"
$cimses = New-CimSession -ComputerName $poste
$p = & {$args} DeviceClass Manufacturer DeviceName FriendlyName DriverName `
InfName DriverVersion DeviceID DriverDate
Get-CimInstance -CimSession $cimses -ClassName Win32_PnPSignedDriver `
-Property $p -Filter 'DeviceName != NULL' |
ForEach-Object {
$dev = Get-CimInstance -CimSession $cimses -ClassName Win32_PnPEntity `
-Property Status, ConfigManagerErrorCode `
-Filter "PNPDeviceID='$($_.DeviceID.Replace('\', '\\'))'"
[pscustomobject]#{
DeviceClass = $_.DeviceClass
Manufacturer = $_.Manufacturer
DeviceName = $_.DeviceName
FriendlyName = $_.FriendlyName
DriverName = $_.DriverName
InfName = $_.InfName
Status = $dev.Status
ConfigManagerErrorCode = $dev.ConfigManagerErrorCode
DriverDate = '{0:yyyyMMddHHmmss}' -f $_.DriverDate
DriverVersion = $_.DriverVersion
}
} | Sort-Object DeviceClass | Out-GridView -Title "$poste - Drivers utilisés"
Remove-CimSession -CimSession $cimses
On my machine, the type of the Win32_PnPSignedDriver.DriverDate member is DateTime.
Get-CimClass -ClassName Win32_PnPSignedDriver |
Select-Object -ExpandProperty CimClassProperties |
Where-Object Name -eq 'DriverDate' |
Select-Object CimType | Format-Table -AutoSize
CimType
-------
DateTime
The bottleneck is searching for a device every time using Where-Object.
The following code uses Group-Object to solve the issue.
$poste = "COMPUTER1234"
$DriversUp = Get-WmiObject -ComputerName $poste Win32_PnPSignedDriver -Filter "DeviceName != NULL"
$Devices = Get-WmiObject -ComputerName $poste Win32_PnPEntity -Property DeviceID,Status,ConfigManagerErrorCode
#($DriversUp; $Devices) | Group-Object DeviceID | Where-Object Count -eq 2 | ForEach-Object {
$driver, $device = $_.Group
[pscustomobject]#{
DeviceClass = $driver.DeviceClass
Manufacturer = $driver.Manufacturer
DeviceName = $driver.DeviceName
FriendlyName = $driver.FriendlyName
DriverName = $driver.DriverName
InfName = $driver.InfName
Status = $device.Status
ConfigManagerErrorCode = $device.ConfigManagerErrorCode
DriverDate = [datetime]::ParseExact($driver.DriverDate.Substring(0, 14), "yyyyMMddHHmmss", $null)
DriverVersion = $driver.DriverVersion
}
} | Sort-Object DeviceClass | Out-GridView -Title "$poste - Drivers utilisés"
This is how I would do it, not big difference in performance, but more clear IMO:
Used an ArrayList for the results, also replaced the Where-Object on the Get-WmiObject with -Filter, it's a bit faster...
$poste = "COMPUTER1234"
$DriversUp = Get-WmiObject -computername $poste Win32_PNPsigneddriver -Filter "DeviceName != NULL"
$Devices = Get-WmiObject -computername $poste Win32_PNPentity
$DriversDevices = New-Object System.Collections.ArrayList
foreach ($driver in $DriversUp) {
$row = "" | Select DeviceClass,Manufacturer,DeviceName,FriendlyName,DriverName,
InfName,Status,ConfigManagerErrorCode,DriverDate,DriverVersion
$row.DeviceClass = $driver.DeviceClass
$row.Manufacturer = $driver.Manufacturer
$row.DeviceName = $driver.DeviceName
$row.DriverName = $driver.DriverName
$row.InfName = $driver.InfName
$row.Status = ($Devices | ? {$_.DeviceID -eq $driver.DeviceID}).Status
$row.ConfigManagerErrorCode = ($Devices | ? {$_.DeviceID -eq $driver.DeviceID}).ConfigManagerErrorCode
$row.DriverDate = [datetime]::ParseExact(($driver.DriverDate.Split('.')[0]),"yyyyMMddHHmmss",$null)
$row.DriverVersion = $driver.DriverVersion
[void]$DriversDevices.Add($row)
}
$DriversDevices | Sort-Object DeviceClass | Out-GridView -Title "$poste - Drivers utilisés"

How to imbricate a command inside another command?

I have the following PowerShell script which allows me to collect information about disks & volumes on Windows servers of the domain where the script is launched:
$ErrorActionPreference = 'SilentlyContinue'
Get-ADComputer -Filter 'OperatingSystem -like "*Server*"' -Properties * |
Select-Object Name |
ForEach-Object {
if (Test-Connection $_.Name -Count 1) {
Get-WmiObject -Class Win32_LogicalDisk -ComputerName $_.Name -Filter "DriveType=3" |
Select-Object PSComputerName, DeviceID,
#{Name="Size /GB";Expression={[math]::Round($($_.Size / 1GB), 2)}},
#{Name="Free /GB";Expression={[math]::Round($($_.Freespace / 1GB), 2)}},
#{Name="Free %";Expression={[math]::Round($($_.Freespace/$_.Size)*100, 1)}}
} else {
Write-Nost $_.Name " Connection Error"
}
} |
sort PSComputerName |
Format-Table -AutoSize
I get the following result:
SRV01 Connection Error
SRV02 Connection Error
PSComputerName DeviceID Size /GB Free /GB Free %
-------------- -------- ------------ --------- -------
SERVER03 C: 125,51 105,59 84,1
SERVER04 C: 24,83 7,38 29,7
SERVER05 E: 14,65 7,36 50,2
SERVER06 C: 49,66 29,28 59
I want to add an additional column with the OS for each server.
I would like this column to be in second position, after the "PSComputerName" column. How can I get this result?
I think I use a nested command by adding a Get-WmiObject Win32_OperatingSystem | Select-Object caption in the Get-WmiObject -Class Win32_LogicalDisk ..., but I don't know which syntax to use and how to imbricate a command in another command.
Don't use -properties *... it's going to retrieve every single populated property which you don't need in this script.
Get-ADComputer has an operatingsystem property.
Not tested:
Get-ADComputer -Filter 'OperatingSystem -like "*Server*"' -Properties OperatingSystem | ForEach-Object {
$OS = $_.OperatingSystem
If (Test-Connection $_.Name -Count 1 -Quiet){
Get-WmiObject -Class win32_logicalDisk -ComputerName $_.Name -Filter "DriveType=3" |
Select-Object pscomputername, #{Name="OS";Expression={$OS}} ,DeviceID,
#{Name="Size /GB";Expression={[math]::Round($($_.size / 1GB), 2)}},
#{Name="Free /GB";Expression={[math]::Round($($_.freespace / 1GB), 2)}},
#{Name="Free %";Expression={[math]::Round($($_.Freespace/$_.Size)*100, 1)}}
}
else {
Write-host $_.Name " Connection Error"
}
} | sort pscomputername | Format-Table -AutoSize

Correlate Physical Device ID to Volume Device ID

I'm trying to utilize WMI via PowerShell to run through SAN storage on remote servers to grab the Windows disk management volume label.
The only way I've found to do this is to correlate the volume device id (\\?\Volume{34243...} with the physical disk device ID (\\.\PHYSICALDRIVE01).
However, I haven't been able to find out how to link those two fields together. Is this possible with WMI?
For volumes that were assigned a drive letter you can correlate disks and volumes like this:
Get-WmiObject Win32_DiskDrive | ForEach-Object {
$disk = $_
$partitions = "ASSOCIATORS OF " +
"{Win32_DiskDrive.DeviceID='$($disk.DeviceID)'} " +
"WHERE AssocClass = Win32_DiskDriveToDiskPartition"
Get-WmiObject -Query $partitions | ForEach-Object {
$partition = $_
$drives = "ASSOCIATORS OF " +
"{Win32_DiskPartition.DeviceID='$($partition.DeviceID)'} " +
"WHERE AssocClass = Win32_LogicalDiskToPartition"
Get-WmiObject -Query $drives | ForEach-Object {
$driveLetter = $_.DeviceID
$fltr = "DriveLetter='$driveLetter'"
New-Object -Type PSCustomObject -Property #{
Disk = $disk.DeviceID
DriveLetter = $driveLetter
VolumeName = $_.VolumeName
VolumeID = Get-WmiObject -Class Win32_Volume -Filter $fltr |
Select-Object -Expand DeviceID
}
}
}
}
Otherwise it doesn't seem possible with WMI.
On Windows 8/Server 2012 or newer you could use the Get-Partition cmdlet, though:
Get-Partition | Select-Object DiskNumber, DriveLetter, #{n='VolumeID';e={
$_.AccessPaths | Where-Object { $_ -like '\\?\volume*' }
}}
I have done a script that collects the most important stuff from volume and disk WMI. its used with getting information from a Remote Desktop server where a lot of disks are mounted but can be hard to find who is using which disk. its using AD to query the user and connect it with the SID to find the file path. so its a matter of first collecting all the data from the different disk commands and then combine the outputs. the most important command to bind disk data with volume data is the get-partition that shows deviceid
Function Get-VHDMount {
[cmdletbinding()]
Param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[ValidateNotNullorEmpty()]
[OBJECT[]]$Computername,
[STRING]$RDSPATH = '\\rdsprofiles'
)
foreach ($computer in $Computername) {
$RDSItems = (Get-ChildItem $RDSPATH -Recurse -Filter *.vhdx)
$VolumeInfo = invoke-command -ComputerName $computer -scriptblock {Get-Volume | select *}
$VHDMountInfo = Get-WmiObject Win32_Volume -ComputerName $computer |where Label -eq 'user Disk'
$partitioninfo = invoke-command -ComputerName $computer -scriptblock {Get-Partition | Select-Object DiskNumber, #{n='VolumeID';e={$_.AccessPaths | Where-Object { $_ -like '\\?\volume*' }}}}
foreach ($VHDmount in $VHDMountInfo) {
$adinfo = Get-ADUser ($VHDmount.name | Split-Path -Leaf)
[PSCUSTOMOBJECT]#{
Computername = $computer
username = $VHDmount.name | Split-Path -Leaf
displayname = $adinfo.name
SID = $adinfo.SID
deviceid = $VHDmount.deviceid
capacity = ([MATH]::ROUND(($VHDmount.capacity) / 1gb))
HealthStatus = ($VolumeInfo | where ObjectId -eq ($VHDmount.deviceid)).HealthStatus
DiskNumber = ($partitioninfo | where Volumeid -eq ($VHDmount.deviceid)).DiskNumber
Path = ($RDSItems | where fullname -like "*$($adinfo.SID)*").FullName
}
}
}
}

Foreach drive letter array

I'm putting together a script that checks whether or not a logical drive is USB or an iSCSI target. And if it is ignore the drive letters associated.
Get-WmiObject win32_logicaldisk -Filter "DriveType='3'" |
where-object{$_.DeviceID -ne $usbletters -and $_.DeviceID -ne $iSCSIletters} | %
{$_.Name} | out-file $kreports\avail.txt
My issue is that when multiple drives are detected of the same type they are simply ignored by the not equal to option. I'm assuming I need to do some form of foreach loop?
If someone could point me in the right direction that would be fantastic!
Here's the full script.
#Variables and Arguments
$kreports = "C:\Kworking\reports"
# Create kworking Reports folder
if (!(Test-Path -path $kreports))
{New-Item $kreports -type directory}
# USB Drive check
$diskdrive = gwmi win32_diskdrive | ?{$_.interfacetype -eq "USB"}
$usbletters = $diskdrive | %{gwmi -Query "ASSOCIATORS OF
{Win32_DiskDrive.DeviceID=`"$($_.DeviceID.replace('\','\\'))`"} WHERE
AssocClass = Win32_DiskDriveToDiskPartition"} | %{gwmi -Query "ASSOCIATORS
OF {Win32_DiskPartition.DeviceID=`"$($_.DeviceID)`"} WHERE AssocClass =
Win32_LogicalDiskToPartition"} | %{$_.DeviceID}
# iSCSI Drive check
$iSCSIdrive = gwmi win32_diskdrive | ?{$_.model -match "iSCSI"}
$iSCSIletters = $iSCSIdrive | %{gwmi -Query "ASSOCIATORS OF
{Win32_DiskDrive.DeviceID=`"$($_.DeviceID.replace('\','\\'))`"} WHERE
AssocClass = Win32_DiskDriveToDiskPartition"} | %{gwmi -Query "ASSOCIATORS
OF {Win32_DiskPartition.DeviceID=`"$($_.DeviceID)`"} WHERE AssocClass =
Win32_LogicalDiskToPartition"} | %{$_.DeviceID}
# Disk Information
Get-WmiObject win32_logicaldisk -Filter "DriveType='3'" | where-object{$_.DeviceID -ne
$usbletters -and $_.DeviceID -ne $iSCSIletters} | %{$_.Name} | out-file
$kreports\avail.txt
# Fix Output Line Spacing
$InputFile = "$kreports\avail.txt"
$OutputFile = "$kreports\availdisks.txt"
$Writer = New-Object IO.StreamWriter "$OutputFile"
$Writer.Write( [String]::Join("`r`n", $(Get-Content $InputFile)) )
$Writer.Close()
The problem is that Win32_LogicalDisk's member DeviceID contains drive letter and colon. As the value is, say, C: and you test for equality against C, the where-object doesn't find anything.
Either include the colon on drive letters or use a regexp the Powershell way.
$disks = gwmi win32_logicaldisk -Filter "DriveType='3'"
# Select all devices that do not have deviceids a,b,c,k or l, followed by colon
$avail = $disks | ? { $_.DeviceID -notmatch "[abckl]:"}
# Do something with filtered results