List physical drive space - powershell

I have around 200 servers and I need to get the disk space & logical drive space details (free space, used space & total space).
Here is my PowerShell query.
$infoObjects = New-Object PSObject
foreach ($machine in $servers) {
$counts = Get-WmiObject -Query "SELECT * FROM Win32_DiskDrive" -ComputerName $machine
$total_disk = #($counts).Count
$i = 0
$total_disk = $total_disk -1
for (; $i -le $total_disk; $i++) {
$a = $i
$a = Get-WmiObject -Query "SELECT * FROM Win32_DiskDrive WHERE DeviceID='\\\\.\\PHYSICALDRIVE$i'" -ComputerName $machine
$b = $i
$b = [math]::round($a.size /1GB)
Add-Member -InputObject $infoObject -MemberType NoteProperty -Name "Physical_Disk $i" -Value $b
}
$infoObject | Export-Csv -Path Inventory.csv -Append -Force -NoTypeInformation
}
It is giving me desired output but if some of serverd have more than one disk or have more logical drive than the output is stuck with that number of drives of first server. It is not giving me output in the CSV file of rest of the drives of other servers.
Here is the example about what I am saying.
ServerName Physical_Disk 0 Physical_Disk 1 Physical_Disk 2 Physical_Disk 3
Server1 100 20 40
Server2 85
Server3 60 450 200 420
Server4 60
Server5 60
Server10 55 20 40
If it seems like I am not able to explain the problem. Let me try again.
First server has 2 physical drives that are coming in my output file (CSV).
Second server also has 2 physical drives that are also in CSV file.
But third server has more than 2 drives and only 2 drives are showing in output.

Export-Csv assumes that all objects in your list are uniform, i.e. that they all have the same properties, just with different values. It takes the properties of the first element to determine what to export. Either make sure that all objects have the same properties, or use a different output method, for instance putting all disk information in an array per host and write that to the output file, e.g. like this:
foreach ($machine in $servers) {
$disks = #($machine)
$disks += Get-WmiObject -Computer $machine -Class Win32_DiskDrive |
ForEach-Object { [Math]::Round($_.Size/1GB) }
$disks -join ',' | Add-Content 'C:\path\to\output.csv'
}
BTW, you don't need multiple WMI queries, since the first one already returns all disks including their sizes.

I would like to point that there is several places where error could occur. The purpose of this answer is to address the unknown number of headers. I would recommend that you run this in place to see what it is trying to show you before you attempt to integrate this.
# Gather all wmi drives query at once
$alldisksInfo = Get-WmiObject –query "SELECT * FROM Win32_DiskDrive" -ComputerName $servers -ErrorAction SilentlyContinue | Group-Object __Server
# Figure out the maximum number of disks
$MaximumDrives = $alldisksInfo | Measure-Object -Property Count -Maximum | Select-Object -ExpandProperty Maximum
# Build the objects, making empty properties for the drives that dont exist for each server where need be.
$servers | ForEach-Object{
# Clean the hashtable
$infoObject = [ordered]#{}
# Populate Server
$infoObject.Server = $_
# Add other simple properties here
$infoObject.PhysicalMemory = (Get-WmiObject Win32_PhysicalMemory -ComputerName $infoObject.Server | Measure-Object Capacity -Sum).Sum/1gb
# Add the disks information from the $diskInfo Array
$serverDisksWMI = $alldisksInfo | Where-Object{$_.Name -eq $infoObject.Server} | Select-Object -ExpandProperty Group
for($diskIndex =0; $diskIndex -lt $MaximumDrives;$diskIndex++){
$infoObject."PhysicalDisk$diskIndex" = [Math]::Round(($serverDisksWMI | Where-Object{($_.DeviceID -replace "^\D*") -eq $diskIndex} | Select -Expand Size)/1GB)
}
# Create the custom object now.
New-Object -TypeName psobject -Property $infoObject
} # | Export-Csv ....
Since this uses the pipeline you can easily add export-CSV on the end of this.

I Have below script:
And I am looking for help to convert the output to Excel format
$Pather = Get-Content C:\Servers.txt
foreach($Path in $Pather)
{
(Get-Acl $Path).access | ft $path,IdentityReference,FileSystemRights,AccessControlType,IsInherited -auto
}

Related

Power Shell Script - Object Oriented Programming

I've been having some issues trying to pass along some variables with the needed output I need.
Summary, I need to grab the CPU utilization of multiple PC, memory of those PC, and HDD usage, I would like the information to come out with what the PC name is followed by each other category of information required listed above.
Below is what I have so far, however, I cant figure out why I cant get the PCName to output for the CPU utilization. The hdd command is working well but I'm stuck trying to string everything together through objects. Thanks for any help.
CPU Script
$computers = Get-Content -Path ()
foreach ($computer in $computers)
$CPUAvg = (get-counter -Counter "\Processor(_Total)\%
Processor Time" -SampleInterval 1 -MaxSamples 5 | select -ExpandProperty countersamples |
select -ExpandProperty cookedvalue | Measure-Object -Average)
Write-Output $CPUAvg
Cant Figure out how to output CPUName for each computer in $computers
HDD Script works fine
$computers = Get-Content -Path ()
Get−CimInstance Win32_LogicalDisk | where{$_.DriveType −eq '3'} `
| Select DeviceID, DriveType,VolumeName,
#{N='TotalSize(GB)';E={[Math]::Ceiling($_.Size/1GB)}}, #{N='FreeSize(GB)';E=
{[Math]::Ceiling($_.FreeSpace/1GB)}} |ft −AutoSize
currently the code always queries the local data and not that one from a remote computer. None of the samples gives the information from the variable $computers back.
In your first sample the foreach statement is missing its expression ({}). But you do not need to loop, as get-counter supports arrays for the parameter "-computername":
get-counter -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -MaxSamples 5 -computername $computers
you get objects back which contain an attribute called CounterSamples, the value contains the computername.
in case of your 2nd sample, you have to specify the parameter "-computername" to specify a list of computers to connect to. also you can do the filtering one step earlier - e.g.
$computers = Get-Content -Path [path]
$result = Get-CimInstance -query "Select DeviceId,DriveType,VolumeName,SystemName from Win32_LogicalDisk where DriveType=3" -ComputerName $computers | Select-Object SystemName,DeviceID,DriveType,VolumeName,#{N='TotalSize(GB)';E={[Math]::Ceiling($_.Size/1GB)}}, #{N='FreeSize(GB)';E={[Math]::Ceiling($_.FreeSpace/1GB)}}
The variable $result contains the information. you can now output the information, like in your sample, only on the screen (ft -Autosize). or you can export it into a csv/json:
#as csv
$result | export-csv C:\my.csv -Delimiter ";" -NoTypeInformation
#as json
$result | convertto-json | set-content C:\my.json -Encoding:utf8
You could also use the invoke-command cmdlet to connect to the remote computers ,query the information and build and return a object containing the necessary information:
$code = {
#Query cpu Info
$counter = get-counter -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -MaxSamples 5
$processorTimeAvg = ($counter.countersamples.cookedvalue | Measure-Object -Average).Average
#Query Disk Info
$diskInfo = Get-CimInstance -query "Select DeviceId,DriveType,VolumeName from Win32_LogicalDisk where DriveType=3" | Select-Object DeviceID,DriveType,VolumeName,#{N='TotalSize(GB)';E={[Math]::Ceiling($_.Size/1GB)}}, #{N='FreeSize(GB)';E={[Math]::Ceiling($_.FreeSpace/1GB)}}
#Build Object to return
$attrsht = #{
computerName=$env:Computername
processorTimeAvg=$processorTimeAvg
deviceId=$diskInfo.DeviceId
driveType=$diskInfo.driveType
volumeName=$diskInfo.volumeName
'totalSize(GB)'=$diskInfo.'totalSize(GB)'
'freeSize(GB)'=$diskInfo.'freeSize(GB)'
}
return New-Object -TypeName psobject -Property $attrsht
}
#Get list of computers
$computers = Get-Content [path]
#Query Computers
$result = Invoke-Command -ComputerName $computers -ScriptBlock $code
#Export result to json
$result | ConvertTo-Json | set-content C:\my.json -Encoding:utf8
Everything works except when a server has two HDD, the code will not output two separate lines of info.
$code = {
#Query cpu Info
$counter = get-counter -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -MaxSamples 5
$processorTimeAvg = ($counter.countersamples.cookedvalue | Measure-Object -Average).Average
#Query Disk Info
$diskInfo = Get-CimInstance Win32_LogicalDisk | where{$_.DriveType -eq '3'} | Select-Object -Property DeviceID, #{'Name' = 'Size (GB)'; Expression={[Math]::Round($_.Size / 1GB,2)}},#{'Name' = 'FreeSpace (GB)'; Expression={[Math]::Round($_.FreeSpace / 1GB,2)}}
#Build Object to return
$attrsht = #{
computerName=$env:Computername
processorTimeAvg=$processorTimeAvg
diskinfo=$diskInfo
}
return New-Object -TypeName psobject -Property $attrsht
}
#Get list of computers
$computers = Get-Content -Path
#Query Computers
$result = Invoke-Command -ComputerName $computers -ScriptBlock $code
#Export result to SAD Folder
$result | Export-Csv -Path

Query list of computers - output last logged on user and last logon date

I am creating a script to retrieve all the machine names from a .txt file then Query against them;
ComputerName
UserName (Of the last person to logon to the machine)
Date it was last Logged on to/Used
This is what i have
Clear-Host
$machines = Get-Content -Path C:\Users\khalifam\Desktop\Winver\MachineNames.txt
ForEach ($Compu in $machines) {
Get-WmiObject –ComputerName $machines –Class Win32_ComputerSystem | Select
Username, PSComputerName | FT
}
As sidenotes:
the hyphens for the parameter names are not hyphens, but En-Dashes, so I gather this code is copied from the internet somewhere
inside the loop you are using the wrong variable on the ComputerName parameter which should be $Compu
Having said that, I don't think you can get the info you need from the WMI Win32_ComputerSystem class..
What you will need to do is to parse the info from the computers eventlog:
# get an array of computernames loaded from the text file
$machines = Get-Content -Path C:\Users\khalifam\Desktop\Winver\MachineNames.txt
$result = foreach ($computer in $machines) {
# test if the compurer is on-line
if (!(Test-Connection -ComputerName $computer -Count 1 -Quiet)) {
Write-Warning "Computer '$computer' is off-line."
# skip this computer and carry on with the next iteration
continue
}
# search the computers eventlog and parse the username and last logon time from that
# you can play around with other values for -MaxEvents if you feel you're missing information.
Get-WinEvent -ComputerName $computer -FilterHashtable #{Logname='Security';ID=4672} -MaxEvents 20 |
Where-Object { $_.Properties[1].Value -notmatch 'SYSTEM|NETWORK SERVICE|LOCAL SERVICE' } |
Select-Object #{Name ='ComputerName'; Expression = {$_.MachineName}},
#{Name ='UserName'; Expression = {$_.Properties[1].Value}},
#{Name ='LastLogon'; Expression = {$_.TimeCreated}} -First 1
}
# show on screen:
$result | Format-Table -AutoSize
# save as CSV file
$result | Export-Csv -Path 'D:\LastLogonInfo.csv' -NoTypeInformation
Update
If I understand your comment correctly, you would like a list of all users (except for a few) and retrieve their latest login on a computer from the list.
In that case you can do the following:
# get an array of computernames loaded from the text file
$machines = Get-Content -Path C:\Users\khalifam\Desktop\Winver\MachineNames.txt
$result = foreach ($computer in $machines) {
# test if the compurer is on-line
if (!(Test-Connection -ComputerName $computer -Count 1 -Quiet)) {
Write-Warning "Computer '$computer' is off-line."
# skip this computer and carry on with the next iteration
continue
}
# you do not want to include these account logins
$exclude = '\$|SYSTEM|NETWORK SERVICE|LOCAL SERVICE|KHALIFAM'
# search the computers eventlog and parse the username and last logon time from that
# you can play around with other values for -MaxEvents if you feel you're missing information.
Get-WinEvent -ComputerName $computer -FilterHashtable #{Logname='Security';ID=4672} -MaxEvents 100 |
Where-Object { $_.Properties[1].Value -notmatch $exclude } |
Select-Object #{Name ='ComputerName'; Expression = {$_.MachineName}},
#{Name ='UserName'; Expression = {$_.Properties[1].Value}},
#{Name ='LastLogon'; Expression = {$_.TimeCreated}} |
Group-Object -Property UserName | ForEach-Object {
$_.Group | Sort-Object LastLogon -Descending | Select-Object -First 1
}
}
# show on screen:
$result | Format-Table -AutoSize
# save as CSV file
$result | Export-Csv -Path 'D:\LastLogonInfo.csv' -NoTypeInformation

Get WMI Data From Multiple Computers and Export to CSV

So having some good old fashion Powershell frustrations today. What I need to do is this:
Get a list of computers from a file
Query those computers for "CSName" and "InstallDate" from Win32_OperatingSystem
Convert InstallDate into a useable date format.
Export all that to a .Csv
I've tried so many different iterations of my script. I run into 2 major issues. One is that I can't export and append to .Csv even with Export-Csv -Append. It just takes the first value and does nothing with the rest. The 2nd is that I can't get the datetime converter to work when piping |.
Here's a few samples of what I've tried - none of which work.
This sample simply errors a lot. Doesn't seem to carry $_ over from the WMI query in the pipe. It looks like it is trying to use data from the first pipe, but I'm not sure.
Get-Content -Path .\Computernames.txt | Foreach-Object {
gwmi Win32_OperatingSystem -ComputerName $_) |
Select-Object $_.CSName, $_.ConvertToDateTime($OS.InstallDate).ToShortDateString()
} | Export-Csv -Path Filename -Force -Append -NoTypeInformation
}
This one simply exports the first value and gives up on the rest when exporting .Csv
$Computers = Get-Content -Path .\Computernames.txt
foreach ($Computer in $Computers) {
echo $Computer
$OS = gwmi Win32_OperatingSystem -ComputerName $Computer
$OS | Select-Object
$OS.CSName,$OS.ConvertToDateTime($OS.InstallDate).ToShortDateString() |
Export-Csv -Path $Log.FullName -Append
}
This one does get the data, but when I try to select anything, I get null values, but I can echo just fine.
$OS = gwmi Win32_OperatingSystem -ComputerName $Computers
$OS | Foreach-Object {
Select-Object $_.CSName,$_.ConvertToDateTime($OS.InstallDate).ToShortDateString() |
Export-Csv -Path $Log.FullName -Force -Append -NoTypeInformation
}
This feels like it should be ridiculously simple. I can do this in C# with almost no effort, but I just can't get PS to do what I want. Any help would be much appreciated!
Here you go,
$Array = #() ## Create Array to hold the Data
$Computers = Get-Content -Path .\Computernames.txt
foreach ($Computer in $Computers)
{
$Result = "" | Select CSName,InstallDate ## Create Object to hold the data
$OS = Get-WmiObject Win32_OperatingSystem -ComputerName $Computer
$Result.CSName = $OS.CSName ## Add CSName to line1
$Result.InstallDate = $OS.ConvertToDateTime($OS.InstallDate).ToShortDateString() ## Add InstallDate to line2
$Array += $Result ## Add the data to the array
}
$Array = Export-Csv c:\file.csv -NoTypeInformation

Gather info through several foreach and then export-csv at the end of script

I have a script that scans all my servers in my domains and outputs to two separate CSV files - one simple and one extensive.
I write one line at the time to my csv.. This results in thousands of file-open and file-close.. I've lurked around and understand that I should first gather all the info and write it all in one sweep at the end of the script. But how do I do this with export-csv (preferably using a function)?
And is there a way I can use the same function for short and long list?
The script performs numerous tasks on each domain/server, but I've trimmed it down to this for your viewing pleasure:
$domains = "no","se","dk"
# Loop through specified forests
foreach ($domain in $domains) {
# List all servers
$servers = Get-QADComputer
# Looping through the servers
foreach ($server in $servers) {
# GENERATE LONGLIST #
# Ping server
if (Test-Connection -ComputerName $server.name -count 1 -Quiet )
{
$Reachable = "Yes"
# Fetch IP address
$ipaddress = [System.Net.Dns]::GetHostAddresses($Server.name)|select-object IPAddressToString -expandproperty IPAddressToString
# Formatting output and export all info to CSV
New-Object -TypeName PSObject -Property #{
SystemName = ($server.name).ToLower()
Reachable = $Reachable
Domain = $server.domain
IPAddress = $IPAddress
} | Select-Object SystemName,Domain,IPAddress| Export-Csv -Path "longexport.csv" -append
}
else # Can't reach server
{
$reachable = "No"
$IPAddress = "Unknown"
# Formatting output and export all info to CSV
New-Object -TypeName PSObject -Property #{
SystemName = ($server.name).ToLower()
Reachable = $Reachable
Domain = $server.domain
} | Select-Object SystemName,Domain,IPAddress| Export-Csv -Path "shortexport.csv" -append
}
}
}
(and let me just say that I know that I cannot do -append with export-csv, but I am using a function that let's me do this..)
You are exporting the same amount of properties to each file so I'm not sure I understand why one of them is considered long and one short. Anyway, I suggest the following, don't assign all computers to a variable, it can take up a lot of RAM, instead use a streaming way (one object at a time) and use foreach-object. Also, since I find no difference in the files I output to the file at the end of each domain operation (one per domain). And with another twick you could write only once to the file.
$domains = "no","se","dk"
foreach ($domain in $domains) {
Get-QADComputer -Service $domain -SizeLimit 0 | Foreach-Object {
$reachable = Test-Connection -ComputerName $_.Name -count 1 -Quiet
if($reachable)
{
$IPAddress = [System.Net.Dns]::GetHostAddresses($_.Name)|select-object IPAddressToString -expandproperty IPAddressToString
}
else
{
$IPAddress = $null
}
New-Object -TypeName PSObject -Property #{
SystemName = $_.Name.ToLower()
Reachable = $reachable
Domain = $_.Domain
IPAddress = $IPAddress
} | Select-Object SystemName,Domain,IPAddress
} | Export-Csv -Path export.csv -Append
}
You'll need to keep data in memory to prevent multiple file open/closes.
You can do this by adding your data to an array like this:
$myData = #()
$myData += New-Object -TypeName PSObject -Property #{
SystemName = ($server.name).ToLower()
Reachable = $Reachable
Domain = $server.domain
} | Select-Object SystemName,Domain,IPAddress
Then at the end of processing convert the array to CSV using $myData | ConvertTo-CSV | Out-File C:\Data.csv or just $myData | Export-Csv C:\Data.csv.

Powershell - how to sort UNC paths by ping response time

I'm trying to make a script that would given a list of UNC shares order them by their ping response time.
I've managed to come up with something that feels quite like a hack and I'm wondering if anyone has any better idea how to do it purely in "powershell spirit"?
This is my ugly solution:
$shares = Get-Content unc_shares.txt | where {Test-Path $_}
$servers = $shares | ForEach-Object {$_.Substring(2, $_.IndexOf("\", 3) - 2)}
$sortedServers = ($servers |
ForEach-Object -Process {(Get-WmiObject Win32_PingStatus -filter "Address='$_'")} |
sort ResponseTime |
select Address)
foreach($server in $sortedServers)
{
$shares | where {$_.Contains($server.Address)} | Out-File $sortedListPath -append
}
When all you want to do is sort, just use a ScriptBlock to sort by:
cat .\uncshares.txt | Sort { Test-Connection -Count 1 -CN $_.split('\')[2] }
Or, you could use an average:
cat .\uncshares.txt | Sort { Test-Connection -Count 3 -CN $_.split('\')[2] | Measure ResponseTime -Average | Select -Expand Average }
It's true that when you want to add data to an object, you should use Add-Member. In this case, you could add a NoteProperty with the result of the ping, but it's more interesting to add script property (or method) called ping that would actually execute the ping. That is, it will do the ping when you call the ping member:
$Shares = cat .\uncshares.txt | Add-Member ScriptProperty Ping -Passthru -Value {
Test-Connection $this.split('\')[2] -Count 1 |
Select -Expand ResponseTime }
# Each time you sort by it, it will re-ping, notice the delay:
$Shares | Sort Ping
You can use the average with this method too:
$Shares = cat .\uncshares.txt | Add-Member ScriptProperty Ping -Passthru -Value {
(Test-Connection $this.split('\')[2] -Count 3 |
Measure ResponseTime -Average).Average }
# But this will take even longer:
$Shares | Sort Ping
As an alternative to Add-Member (when you do NOT want to re-ping every time), you can build up objects using Select-Object, so you could create a Ping object, and then add the Share name back to it like so:
$unc = cat .\uncshares.txt
## Gotta love backslashes in regex. Not...
$unc -replace '\\\\([^\\]+)\\.*','$1' |
Test-Connection -Count 1 -CN {$_} |
Sort ResponseTime |
Select #{n='Server';e={$_.Address}},
#{n='Share'; e={$unc -match $_.Address}},
#{n='Ping'; e={$_.ResponseTime}}
That allows you to have drastically different output because you're combining multiple objects together...
Here's a "folded" one-liner:
#(foreach ($unc in $list){
test-connection $unc.split("\")[2] |
measure responsetime -average |
% {$_.average.tostring() + " $unc"}}) |
sort |% {$_.split()[1]}
If you want to save and display the ResponseTimes, replace that last line with:
sort | select #{l="Share";e={$_.split()[1]}},#{l="ResponseTime";e={"{0:F2}" -f $_.split()[0]}}