Modifying the Powershell calculated property with directory size - powershell

I am trying to modify the below script to add one more column 'Directory size' so that it shows the total directory size for NTFS share.
Function Get-SmbSharePermission([String]$ComputerName = '.') {
$Shares = #{}
Get-WmiObject -Class Win32_Share -ComputerName $ComputerName | ForEach-Object {$Shares[$_.Name] = $_.Path}
Get-WmiObject -Class Win32_LogicalShareSecuritySetting -ComputerName $ComputerName | ForEach-Object {
$Name = $_.Name
$_.GetSecurityDescriptor().Descriptor.DACL | Select-Object -Property `
#{n='ComputerName'; e={$ComputerName}},
#{n='Name'; e={$Name}},
#{n='Path'; e={$Shares[$Name]}},
#{n='Account'; e={"$($_.Trustee.Domain)\$($_.Trustee.Name)".Trim('\')}},
#{n='Permission'; e={
Switch ($_.AccessMask) {
0x001F01FF {'Full'}
0x001301BF {'Change'}
0x001200A9 {'Read'}
default {"0x$($_.ToString('X8'))"}
}
}},
#{n='Type'; e={If ($_.AceType) {'Deny'} Else {'Allow'}}}
}
}
Get-SmbSharePermission | ogv
The script above does almost everything, except the total size of the directory.
How can I integrate it with the below function that can show the directory size?
$scriptBlock = {
param ([string]$Path,
[string]$GroupName)
# Get the total directory number and file counts
# the '/L' [for List] tells robocopy to not do anything, just list what it _would_ do
# /E :: copy subdirectories, including Empty ones.
# /L :: List only - don't copy, timestamp or delete any files.
# /NFL :: No File List - don't log file names.
# /NDL :: No Directory List - don't log directory names.
# /NP :: No Progress - don't display percentage copied.
$RC_Results = robocopy $Path 'NULL' /L /E /NP /NFL /NDL
# check to see if i forgot to re-enable the robocopy run [*blush*]
if ($RC_Results.Count -eq 0)
{
Write-Warning "You need to remove the `#` on line 2."
break
}
# the summary table is the first 5 of the last 7 lines
$RC_Summary = $RC_Results |
Select-Object -Last 7 |
Select-Object -First 5
# add a 'Type' column header to the 1st line
$RC_Summary[0] = 'Type' + $RC_Summary[0]
# add placeholders for the blank 'Times' [last line] columns
$RC_Summary[-1] = $RC_Summary[-1].
Insert(39, 'N/A').
Insert(49, 'N/A')
$NewRC_Summary = #()
# line cleanup
foreach ($Line in $RC_Summary)
{
# remove leading and trailing spaces
$NewLine = $Line.Trim()
# remove ':' that are NOT in time spans
$NewLine = $NewLine.Replace(' :', '')
# replace multi-spaces with singletons
$NewLine = $NewLine -replace '\s{2,}', ' '
# remove space between number and unit letter [bytes, kilobytes, etc.]
# 1.22 m >> 1.22m
$NewLine = $NewLine -replace '(\d)\s([bkmg])', '$1$2'
# replace remaining spaces with a comma
$NewLine = $NewLine.Replace(' ', ',')
# add to the new summary collection
$NewRC_Summary += $NewLine
}
$RC_SummaryTable = #{ }
foreach ($Item in ($NewRC_Summary | ConvertFrom-Csv))
{
$RC_SummaryTable.Add($Item.Type, $Item.Total)
}
[PSCustomObject] #{
DirPath = $Path
DirCount = "{0}" -f $RC_SummaryTable['Dirs']
FileCount = "{0}" -f $RC_SummaryTable['Files']
TotalSize = "{0}" -f $RC_SummaryTable['Bytes']
}
}
Update
The suggested code by Cpt. Whale below shows the whole disk drive size not each directories under Path:

I prefer to use volume information rather than doing a complete file listing via robocopy. I'm using the newer CimInstance powershell cmdlets here to get the volume information via Get-SmbShare:
Function Get-SmbSharePermission([String]$ComputerName = '.') {
# Create a cim session to use later, and get a list of the remote share objects:
$cim = New-CimSession -ComputerName $Computername
$shares = Get-SmbShare -CimSession $cim | Where Name -NotIn 'print$','IPC$'
# Iterate through each share
Foreach ($Share in $shares) {
# Skip shares without volume info (not file shares, e.g. shared printers)
if(!$Share.Volume){continue}
# Get the assoviated volume size
$Volume = Get-Volume -CimSession $cim -UniqueId $Share.Volume
# Get the access information, and format
Get-SmbShareAccess -CimSession $cim -Name $Share.Name | Select `
#{n='ComputerName'; e={$ComputerName}},
#{n='Name'; e={$Share.Name}},
#{n='Path'; e={$Share.Path}},
#{n='Account'; e={$_.AccountName}},
#{n='Permission'; e={$_.AccessRight}},
#{n='Type'; e={$_.AccessControlType}},
#{n='Drive'; e={$Volume.DriveLetter}},
#{n='TotalSize'; e={[String][Math]::Round($Volume.Size/1GB)+' GB'}},
#{n='FreeSpace'; e={[String][Math]::Round($Volume.SizeRemaining/1GB)+' GB'}}
}
}
# Output:
Get-SmbSharePermission | ft
ComputerName Name Path Account Permission Type Drive TotalSize FreeSpace
------------ ---- ---- ------- ---------- ---- ----- --------- ---------
RemoteServer ADMIN$ C:\WINDOWS BUILTIN\Administrators Full Allow C 100 GB 20 GB
RemoteServer ADMIN$ C:\WINDOWS BUILTIN\Backup Operators Full Allow C 100 GB 20 GB
RemoteServer ADMIN$ C:\WINDOWS NT AUTHORITY\INTERACTIVE Full Allow C 100 GB 20 GB

Related

Resolve-Path: cannot find path because it does not exist. error in powershell

Firstly: what My code does it counts the number of duplicate files in a particular path entered by the user.
It does this by first calculating which files have the same length. and from the files that have the same length, it checks which files also have the same hash. And then in the end I am making an object which contains the information that I need to print in a table.
Problem:
In my code,
I am getting an error which I am pretty sure is coming from the Get-FileHash commandlet.
when I ctrl + click on the link that is given in the "At" section of the error, It takes me to the Get-FileHash commandlet utility in powershell
C: >WIndows > system32 > WindowsPowershell > v1.0 > Modules > Microsoft.Powershell.Utility > Microsoft.Powershell.Utility.psm1 > Get-FileHash{}
the error only comes up for some .msg and .pdf files
the code works perfectly fine on a path with lesser files.
Here is the Error:
Resolve-Path : Cannot find path '<path of File>' because it does not exist.
At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Utility\Micro
+ ... $pathsToProcess += Resolve-Path -LiteralPath $LiteralPath | Forea ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (<nameOfFile>:String)
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.ResolvePathComm
And here is the code:
#taking the input of the user!
$pathInputByUser = Read-Host ("Please Enter the Path where you want to check for duplicate files")
#Accessing the path entered by the user
cd $pathInputByUser
write-host ("Loading: Please Wait! This can take a upto 2.5 hrs") -ForegroundColor Green
$Stopwatch = [System.Diagnostics.Stopwatch]::StartNew();
$totalNumOfFiles = 0;
#the variable "filesGroupedAccordingToLength" contains all the files grouped according to their size. files with same size , are in the same group
$filesGroupedAccordingToLength = Get-ChildItem -Recurse -File `
| Group-Object -Property Length
#below calculating the number of total Files that we have found till now
$totalNumOfFiles = $filesGroupedAccordingToLength | Measure-Object -Property Count -Sum | % { $_.sum };
#the below variable "filesWhichHaveSameLengths" contains group of files that have the same size as some other file
$filesWhichHaveSameLengths = $filesGroupedAccordingToLength | ? { $_.Count -gt 1 } `
| % { $_.Group }
#the below variable "hash" contains group of files that have the same hash as some other file
$groupedFilesWithSameHash = $filesWhichHaveSameLengths | Get-FileHash `
| Group-Object -Property Hash `
| ? { $_.Count -gt 1 }
$numOfDuplicates= 0;
$calculatingCountDuplicates= $groupedFilesWithSameHash| Select-Object -ExpandProperty Count| %{ $numOfDuplicates= $numOfDuplicates + ($_ - 1) };
"The number of duplicate files are: $numOfDuplicates"
#the below variables contains hash of duplicate files
$hashOfDuplicateFiles= $groupedFilesWithSameHash | % { $_.Group }
#making objects to be able to display result as a table
$res= #();
$hashValue=0;
foreach ($tuple in $hashOfDuplicateFiles) {
foreach ($var in $filesWhichHaveSameLengths) {
if ($var.FullName -eq $tuple.Path) {
$obj= new-object psobject -Property #{
LastWriteTime = $var.LastWriteTime
Name = $var.Name
length = $var.length
FullName = $var.FullName
hash = $tuple.Hash
RelativePath = $var.PSPath.Replace("Microsoft.PowerShell.Core\FileSystem::F:\", "")
}
if($tuple.Hash -ne $hashValue){
$obj|Add-Member -NotePropertyName U/D -NotePropertyValue "Unikat"
$res= $res+$obj;
$hashValue= $tuple.Hash;
}
else{
$obj|Add-Member -NotePropertyName U/D -NotePropertyValue "Duplikat"
$res= $res+$obj;
}
}
}
}
$res | Format-Table -AutoSize -Property Name, U/D, LastWriteTime, hash, length, RelativePath |Out-File -FilePath C:\Users\Public\Documents\Ebrahim_iris\DuplicateFinderTool\testLog.txt
"the number of total files is : $totalNumOfFiles"
"The number of duplicate files are: $numOfDuplicates"
"The number of unique files are: $($totalNumOfFiles-$numOfDuplicates)"
$Stopwatch.Stop();
"The script ran for: $($Stopwatch.Elapsed.TotalMinutes) minutes"
Any advice to remove this error will be appreciated.

powershell get info about computer

I'm trying to create a powershell script (getting more advanced... JK. Powershell offers more features than the batch file, and I want to use some of them.)
So, here's my batch script:
:Start
#echo off
set /p password="Password:"
:Nextcomp
set /p computer="Computer name:"
wmic /user:username /password:%password% /node:"%computer%" memorychip get capacity
set /P c=Do you want to get info about another computer (y/n)?
if /I "%c%" EQU "y" goto :Nextcomp
if /I "%c%" EQU "n" goto :End goto :choice
pause
:End
And here's what I found: Script
I modified it for my needs, but whenever I try to run this script, I get it the wrong way - it's displaying me the entire script, and only in the end is it asking me about the computer name:
$resultstxt = "C:\Users\user\Documents\results.csv"
Param(
[Parameter(Mandatory=$true, Position=0, HelpMessage="Password?")]
[SecureString]$password
)
$pw = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
$Computer = Read-Host -Prompt 'Computer name'
$out = #()
If (!(Test-Connection -ComputerName $Computer -Count 1 -Quiet)) {
Write-Host "$Computer not on network."
Continue
}
foreach($object in $HostList) {
$RAM = get-wmiobject -user user -password $pw -computername $object.("Computer")-class win32_physicalmemory
$DeviceInfo= #{}
$DeviceInfo.add("RAM", "$([math]::floor($RAM.Capacity/ (1024 * 1024 * 1024 )) )" + " GB" )
$DeviceInfo.add("Computer Name", $vol.SystemName)
$out += New-Object PSObject -Property $DeviceInfo | Select-Object "RAM"
Write-Verbose ($out | Out-String) -Verbose
$out | Export-CSV -FilePath $resultstxt -NoTypeInformation
}
As you might have guessed, I have a lot more fields, but they all are similar, and I borrowed from a lot of sources, but mainly from the "Script" link.
What I want is:
Hide the password
Export the information to CSV, with each new computer (see 3.) added following the current computer (on the next line)
Ask if I want to get the info about another computer, with "y" key for yes, "n" for no.
Make the script work
I found about the problem 1, but I haven't tested it yet, so... will it work? Next, I found about problem 2, but it would display all info in a not-easy-to-read format, and not everything I need, and all in one cell. Finally, about 3, I found, but it wouldn't work. I can't say I dug the entire Internet, but I'm hoping you guys (and gals?) can help me figure it out. It shouldn't be that hard to resolve these 3 issues, it's not a super complicated script after all, right? My current script is only 31 lines, including the whitespaces.
this is a demo of one way to get basic system info from a group of systems. it uses the CIM cmdlets since they are faster than the WMI cmdlets [most of the time], present datetime info as standard datetime objects, AND not somewhat deprecated.
it also uses the Invoke-Command cmdlet for remote parallelism, and is set to ignore errors so that non-responding systems don't waste your time.
#requires -RunAsAdministrator
# fake reading in a list of computer names
# in real life, use Get-Content or (Get-ADComputer).Name
$ComputerList = #'
Localhost
BetterNotBeThere
127.0.0.1
10.0.0.1
::1
'# -split [environment]::NewLine
$IC_ScriptBlock = {
$CIM_ComputerSystem = Get-CimInstance -ClassName CIM_ComputerSystem
$CIM_BIOSElement = Get-CimInstance -ClassName CIM_BIOSElement
$CIM_OperatingSystem = Get-CimInstance -ClassName CIM_OperatingSystem
$CIM_Processor = Get-CimInstance -ClassName CIM_Processor
$CIM_LogicalDisk = Get-CimInstance -ClassName CIM_LogicalDisk |
Where-Object {$_.Name -eq $CIM_OperatingSystem.SystemDrive}
[PSCustomObject]#{
LocalComputerName = $env:COMPUTERNAME
Manufacturer = $CIM_ComputerSystem.Manufacturer
Model = $CIM_ComputerSystem.Model
SerialNumber = $CIM_BIOSElement.SerialNumber
CPU = $CIM_Processor.Name
SysDrive_Capacity_GB = '{0:N2}' -f ($CIM_LogicalDisk.Size / 1GB)
SysDrive_FreeSpace_GB ='{0:N2}' -f ($CIM_LogicalDisk.FreeSpace / 1GB)
SysDrive_FreeSpace_Pct = '{0:N0}' -f ($CIM_LogicalDisk.FreeSpace / $CIM_LogicalDisk.Size * 100)
RAM_GB = '{0:N2}' -f ($CIM_ComputerSystem.TotalPhysicalMemory / 1GB)
OperatingSystem_Name = $CIM_OperatingSystem.Caption
OperatingSystem_Version = $CIM_OperatingSystem.Version
OperatingSystem_BuildNumber = $CIM_OperatingSystem.BuildNumber
OperatingSystem_ServicePack = $CIM_OperatingSystem.ServicePackMajorVersion
CurrentUser = $CIM_ComputerSystem.UserName
LastBootUpTime = $CIM_OperatingSystem.LastBootUpTime
}
}
$IC_Params = #{
ComputerName = $ComputerList
ScriptBlock = $IC_ScriptBlock
ErrorAction = 'SilentlyContinue'
}
$RespondingSystems = Invoke-Command #IC_Params
$NOT_RespondingSystems = $ComputerList.Where({
# these two variants are needed to deal with an ipv6 localhost address
"[$_]" -notin $RespondingSystems.PSComputerName -and
$_ -notin $RespondingSystems.PSComputerName
})
# if you want to remove the PSShowComputerName, PSComputerName & RunspaceID props, use ...
# Select-Object -Property * -ExcludeProperty PSShowComputerName, PSComputerName, RunspaceId
'=' * 40
$RespondingSystems
'=' * 40
$NOT_RespondingSystems
truncated output ...
LocalComputerName : [MySysName]
Manufacturer : System manufacturer
Model : System Product Name
SerialNumber : System Serial Number
CPU : AMD Phenom(tm) II X4 945 Processor
SysDrive_Capacity_GB : 931.41
SysDrive_FreeSpace_GB : 745.69
SysDrive_FreeSpace_Pct : 80
RAM_GB : 8.00
OperatingSystem_Name : Microsoft Windows 7 Professional
OperatingSystem_Version : 6.1.7601
OperatingSystem_BuildNumber : 7601
OperatingSystem_ServicePack : 1
CurrentUser : [MySysName]\[MyUserName]
LastBootUpTime : 2019-01-24 1:49:31 PM
PSComputerName : [::1]
RunspaceId : c1b949ef-93af-478a-b2cf-e44d874c5724
========================================
BetterNotBeThere
10.0.0.1
to get a well structured CSV file, send the $RespondingSystems collection to the file via Export-CSV.
for a demo of a loop to wrap around any given block of code, take a look at this ...
$Choice = ''
while ([string]::IsNullOrEmpty($Choice))
{
$Choice = Read-Host 'Please enter a valid computer name or [x] to exit '
# replace below with real code to check if $ComputerName is valid
if ($Choice -eq $env:COMPUTERNAME)
{
$ValidCN = $True
}
else
{
$ValidCN = $False
}
if (-not $ValidCN -and $Choice -ne 'x')
{
# insert desired error notice
[console]::Beep(1000, 300)
Write-Warning ''
Write-Warning ('Your choice [ {0} ] is not a valid computer name.' -f $Choice)
Write-Warning ' Please try again ...'
pause
$Choice = ''
}
elseif ($Choice -ne 'x')
{
# insert code to do the "ThingToBeDone"
Write-Host ''
Write-Host ('Doing the _!_ThingToBeDone_!_ to system [ {0} ] ...' -f $Choice)
pause
$Choice = ''
}
}
on screen output ...
Please enter a valid computer name or [x] to exit : e
WARNING:
WARNING: Your choice [ e ] is not a valid computer name.
WARNING: Please try again ...
Press Enter to continue...:
Please enter a valid computer name or [x] to exit : [MySysName]
Doing the _!_ThingToBeDone_!_ to system [ [MySysName] ] ...
Press Enter to continue...:
Please enter a valid computer name or [x] to exit : x

How to process out put of diskpart command using PowerShell

I'm trying to capture volume number from diskpart command output. At present I'm doing it like this:
Using batch script:
#echo off
set VOLNO=''
set DRIVE=C
for /f "tokens=2,3" %%a in ('echo list volume ^| diskpart') do (
if %%b==%DRIVE% set VOLNO=%%a
)
echo Volume No. for C Drive is: %VOLNO%
Output:
Volume No. for C Drive is: 2
Using PowerShell:
$dp = "list volume" | diskpart | ? { $_ -match "^ [^-]" }>
$dp
Output:
Volume ### Ltr Label Fs Type Size Status Info
Volume 0 E DVD-ROM 0 B No Media
Volume 1 System Rese NTFS Partition 350 MB Healthy System
Volume 2 C NTFS Partition 59 GB Healthy Boot
Volume 3 D New Volume NTFS Partition 49 GB Healthy
I want to capture the Volume-Number e.g. it's 2 for C:\ to a variable using PowerShell.
Adjust your regular expression so that you can extract the desired information via capturing groups. I generally recommend building custom objects from the extracted information, to facilitate further processing.
Something like this should do what you want:
$drive = 'c'
'list volume' | diskpart | Where-Object {
$_ -match 'Volume (\d+)\s+([a-z])\s+'
} | ForEach-Object {
New-Object -Type PSObject -Property #{
'DriveLetter' = $matches[2]
'VolumeNumber' = [int]$matches[1]
}
} | Where-Object {
$_.DriveLetter -eq $drive
} | Select-Object -Expand VolumeNumber
A concise solution that uses -match to find the line of interest and the unary form of -split to split it into whitespace-separated tokens that can be accessed by index:
$drive = 'C'
$volNo = (-split (('list volume' | diskpart) -match " $drive "))[1]
"Volume No. for $drive Drive is: $volNo"
Note that diskpart must be run from an elevated session.

Retrieve data from last line in vmware.log file?

I currently have a script that retrieves the last modified date of the .vmx in a VM's datastore in vCenter. I need to make changes to instead use and display the last date in the vmware.log file (located in the same datastore as the .vmx)
I'm not sure how to grab that line and convert it to a XX/XX/XXXX format. In the log file, it shows it as Dec 23 10 for example. If this is not possible, no worries. I just need to pull the last line in the log file and export it to a .csv file. Below is my current code:
add-pssnapin VMware.VimAutomation.Core
# ---------- Only modify the fields in this area -------------
$vCenter = 'qlab-copsmgr' #name of the vCenter
$dataCenter = 'Fly-away Kit' #name of the DataCenter
$outputFile = $vCenter + '-LastDateUsed.csv' #desired output file name
# ---------- No modification is needed in the below code. Do not edit -------------
$columnName = "Name,DataStore,Date Last Used" | Out-File .\$OutputFile -Encoding ascii
Connect-VIServer $vCenter -WarningAction SilentlyContinue
$vmList = Get-VM | where { $_.PowerState -eq “PoweredOff”} | select Name
$vmList = $vmList -replace 'Name : ', '' -replace '#{Name=', '' -replace '}', ''
ForEach ($VM in $vmList)
{
# Get configuration and path to vmx file
$VMconfig = Get-VM $VM | Get-View | select config
$VMXpath = $VMconfig.config.files.VMpathName
# Remove and/or replace unwanted strings
$VMXpath = $VMXpath -replace '\[','' -replace '\] ','\' -replace '#{Filename=','/' -replace '}','' -replace '/','\'
# List the vmx file in the datastore
$VMXinfo = ls vmstores:\$VCenter#443\$DataCenter\$VMXpath | Where {$_.LastWriteTime} | select -first 1 | select FolderPath, LastWriteTime
# Remove and/or replace unwanted strings
$VMXinfo = $VMXinfo -replace 'DatastoreFullPath=', '' -replace '#{', '' -replace '}', '' -replace ';', ',' -replace 'LastWriteTime=', ''
# Output vmx information to .csv file
$output = $VM + ', ' + $VMXinfo
$output
echo $output >> $OutputFile
}
I also needed to pull the last event from the vmware.log file in order to backtrack the power off time for VMs where there is no vCenter event history. I looked at file timestamps but found that some VM processes and possibly backup solutions can make them useless.
I tried reading the file in place but ran into issues with the PSDrive type not supporting Get-Content in place. So for better or worse for my solution I started with one of LucD's scripts - the 'Retrieve the logs' script from http://www.lucd.info/2011/02/27/virtual-machine-logging/ which pulls a VMs vmware.log file and copies it to local storage. I then modified it to copy the vmware.log file to a local temp folder, read the last line from the file before deleting the file and return the last line of the log as a PS object.
Note, this is slow and I'm sure my hacks to LucD's script are not elegant, but it does work and I hope if helps someone.
Note: This converts the time value from the log to a PS date object by simple piping the string timestamp from the file into Get-Date. I've read that this does not work as expected for non-US date formatting. For those outside of the US you might want to look into this or just pass the raw timestamp string from the log instead of converting it.
#Examples:
#$lastEventTime = (Get-VM -Name "SomeVM" | Get-VMLogLastEvent).EventTime
#$lastEventTime = Get-VMLogLastEvent -VM "SomeVM" -Path "C:\alternatetemp\"
function Get-VMLogLastEvent{
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)][PSObject[]]$VM,
[string]$Path=$env:TEMP
)
process{
$report = #()
foreach($obj in $VM){
if($obj.GetType().Name -eq "string"){
$obj = Get-VM -Name $obj
}
$logpath = ($obj.ExtensionData.LayoutEx.File | ?{$_.Name -like "*/vmware.log"}).Name
$dsName = $logPath.Split(']')[0].Trim('[')
$vmPath = $logPath.Split(']')[1].Trim(' ')
$ds = Get-Datastore -Name $dsName
$drvName = "MyDS" + (Get-Random)
$localLog = $Path + "\" + $obj.Name + ".vmware.log"
New-PSDrive -Location $ds -Name $drvName -PSProvider VimDatastore -Root '\' | Out-Null
Copy-DatastoreItem -Item ($drvName + ":" + $vmPath) -Destination $localLog -Force:$true
Remove-PSDrive -Name $drvName -Confirm:$false
$lastEvent = Get-Content -Path $localLog -Tail 1
Remove-Item -Path $localLog -Confirm:$false
$row = "" | Select VM, EventType, Event, EventTime
$row.VM = $obj.Name
($row.EventTime, $row.EventType, $row.Event) = $lastEvent.Split("|")
$row.EventTime = $row.EventTime | Get-Date
$report += $row
}
$report
}
}
That should cover your request, but to expound further on why I needed the detail, which reading between the lines may also benefit you, I'll continue.
I inherited hundreds of legacy VMs that have been powered off from various past acquisitions and divestitures and many of which have been moved between vCenter instances losing all event log detail. When I started my cleanup effort in just one datacenter I had over 60TB of powered off VMs. With the legacy nature of these there was also no detail available on who owned or had any knowledge of these old VMs.
For this I hacked another script I found, also from LucD here: https://communities.vmware.com/thread/540397.
This will take in all the powered off VMs, attempt to determine the time powered off via vCenter event history. I modified it to fall back to the above Get-VMLogLastEvent function to get the final poweroff time of the VM if event log detail is not available.
Error catching could be improved - this will error on VMs where for one reason or another there is no vmware.log file. But quick and dirty I've found this to work and provides the detail on what I need for over 90%.
Again this relies on the above function and for me at least the errors just fail through passing through null values. One could probably remove the errors by adding a check for vmware.log existance before attempting to copy it though this would add a touch more latency in execution due to the slow PSDrive interface to datastores.
$Report = #()
$VMs = Get-VM | Where {$_.PowerState -eq "PoweredOff"}
$Datastores = Get-Datastore | Select Name, Id
$PowerOffEvents = Get-VIEvent -Entity $VMs -MaxSamples ([int]::MaxValue) | where {$_ -is [VMware.Vim.VmPoweredOffEvent]} | Group-Object -Property {$_.Vm.Name}
foreach ($VM in $VMs) {
$lastPO = ($PowerOffEvents | Where { $_.Group[0].Vm.Vm -eq $VM.Id }).Group | Sort-Object -Property CreatedTime -Descending | Select -First 1
$lastLogTime = "";
# If no event log detail, revert to vmware.log last entry which takes more time...
if (($lastPO.PoweredOffTime -eq "") -or ($lastPO.PoweredOffTime -eq $null)){
$lastLogTime = (Get-VMLogLastEvent -VM $VM).EventTime
}
$row = "" | select VMName,Powerstate,OS,Host,Cluster,Datastore,NumCPU,MemMb,DiskGb,PoweredOffTime,PoweredOffBy,LastLogTime
$row.VMName = $vm.Name
$row.Powerstate = $vm.Powerstate
$row.OS = $vm.Guest.OSFullName
$row.Host = $vm.VMHost.name
$row.Cluster = $vm.VMHost.Parent.Name
$row.Datastore = $Datastores | Where{$_.Id -eq ($vm.DatastoreIdList | select -First 1)} | Select -ExpandProperty Name
$row.NumCPU = $vm.NumCPU
$row.MemMb = $vm.MemoryMB
$row.DiskGb = Get-HardDisk -VM $vm | Measure-Object -Property CapacityGB -Sum | select -ExpandProperty Sum
$row.PoweredOffTime = $lastPO.CreatedTime
$row.PoweredOffBy = $lastPO.UserName
$row.LastLogTime = $lastLogTime
$report += $row
}
# Output to screen
$report | Sort Cluster, Host, VMName | Select VMName, Cluster, Host, NumCPU, MemMb, #{N='DiskGb';E={[math]::Round($_.DiskGb,2)}}, PoweredOffTime, PoweredOffBy | ft -a
# Output to CSV - change path/filename as appropriate
$report | Sort Cluster, Host, VMName | Export-Csv -Path "output\Powered_Off_VMs_Report.csv" -NoTypeInformation -UseCulture
Cheers!
I pray this pays back some of the karma I've used.
Meyeaard
I have made a script that checks line by line and if string is found changes it to desired format
#example input you can use get-content PATH to txt or any file and assign it to $lines variable
$lines = #"
ernfoewnfnsf
ernfoewnfnsf
Dec 23 10 sgdsgdfgsdadasd
"# -split "\r\n"
#checks line by line and if find anything that maches start of the line, one Big letter two small, space, two digits, space, two digits, space
$lines | ForEach-Object{
if ($_ -match "^[A-Z][a-z]{2}\s\d{2}\s\d{2}\s")
{
$match = [convert]::ToDateTime($matches[0])
$_ -replace $matches[0], "$($match.ToShortDateString()) " | out-file { PATH } -APPEND
}
else
{
$_ | out-file { PATH } -APPEND
}
}
just change {PATH} with a filenamePAth and this should work for you

Using PowerShell to replicate right clicking a folder and selecting properties

I am trying to gather the Size/Size on disk and number of files/folders on a very large folder tree.
I have been using a script like the follow to gather some of this:
Get-ChildItem "C:\test" -recurse | Measure-Object -Sum Length | Select-Object `
#{Name="Path"; Expression={$directory.FullName}},
#{Name="Files"; Expression={$_.Count}},
#{Name="Size"; Expression={$_.Sum}}
Path Files Size
---- ----- ----
C:\test 470 11622961
But when I want to gather information on the number of folders and size on disk I'm having to run a separate script; which recuse through the folder tee again (Which takes a long time).
Is there an easy way of accessing the all this information the same way you can get it when you right click a folder and select properties shown below?
Are there any callable .exe files within system32 that can do this?
According to this answer in the Technet forums you can calculate the size on disk like this:
$afz = [MidpointRounding]::AwayFromZero
[math]::Round($_.Length / $clusterSize + 0.5, $afz) * $clusterSize
$clusterSize can be determined with the fsutil command (e.g. for drive C:):
PS C:\> fsutil fsinfo ntfsinfo C:\
NTFS Volume Serial Number : 0x648ac3ae16817308
Version : 3.1
Number Sectors : 0x00000000027ccfff
Total Clusters : 0x00000000004f99ff
Free Clusters : 0x0000000000158776
Total Reserved : 0x00000000000003e0
Bytes Per Sector : 512
Bytes Per Physical Sector : 512
Bytes Per Cluster : 4096
Bytes Per FileRecord Segment : 1024
Clusters Per FileRecord Segment : 0
...
Note that running fsutil requires admin privileges.
With that you can collect the information you're interested in like this:
$rootDir = "C:\test"
$afz = [MidpointRounding]::AwayFromZero
$clusterSize = fsutil fsinfo ntfsinfo (Get-Item $rootDir).PSDrive.Root `
| Select-String 'Bytes Per Cluster' `
| % { $_.ToString().Split(':')[1].Trim() }
$stat = Get-ChildItem $rootDir -Recurse -Force `
| select Name, Length, #{n="PhysicalSize";e={
[math]::Round($_.Length / $clusterSize + 0.5, $afz) * $clusterSize
}}, #{n="Folder";e={[int]($_.PSIsContainer)}},
#{n="File";e={[int](-not $_.PSIsContainer)}} `
| Measure-Object -Sum Length, PhysicalSize, Folder, File
$folder = New-Object -TypeName PSObject -Property #{
"FullName" = $rootDir;
"Files" = ($stat | ? { $_.Property -eq "File" }).Sum;
"Folders" = ($stat | ? { $_.Property -eq "Folder" }).Sum;
"Size" = ($stat | ? { $_.Property -eq "Length" }).Sum;
"SizeOnDisk" = ($stat | ? { $_.Property -eq "PhysicalSize" }).Sum - $clusterSize;
}
You're going to have to accumulate your data in a custom object as you see each item:
$path = "C:\Users\aaron\Projects\Carbon"
$properties = New-Object PsObject -Property #{ 'Path' = $path; 'Files' = 0; 'Folders' = 0; 'Size' = 0 }
Get-ChildItem -Path $path -Recurse |
ForEach-Object {
if( $_.PsIsContainer )
{
$properties.Folders++
}
else
{
$properties.Size += $_.Length
$properties.Files++
}
}
$properties