How to process out put of diskpart command using PowerShell - 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.

Related

Modifying the Powershell calculated property with directory size

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

How to know, After which Partition unallocated space is available ? (Powershell or Command)

I am trying to use Command or PowerShell to know where on a disk unallocated space is available.
For example below the unallocated space is present after G: drive (or Partition 1).
Using the command echo list disk | diskpart I can only know the Unallocated space.
Is there any way to know this information?
Solution for Windows 8/2012 Server or newer:
I think you could do this (needs to be run with Administrator rights) to return an object with each volume that can be extended (from which you can then deduce there is free space after the volume):
Get-Volume | Where DriveLetter -ne $null | ForEach-Object {
$Size = Get-PartitionSupportedSize -DriveLetter $_.DriveLetter
If ($Size.SizeMax -gt $_.Size) { $_ }
}
Get-Volume | Where DriveLetter -ne $null gets all drives that have a letter
$Size = Get-PartitionSupportedSize -DriveLetter $_.DriveLetter gets the sizemin and sizemax of each drive
If ($Size.SizeMax -gt $_.Size) { $_ } returns the volumes which can be extended (their size max is bigger than the current volume size).

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

Another grep / awk q, parsing diskpart output

I've googled this a lot and there are a lot of similar questions but I can't figure out how to put them together to make it work for me. Also, the fact that MS decided to leave dynamic volumes out of their PowerShell cmdlets is really frustrating.
In the following code I'm trying to identify that "Disk 2" is dynamic.
PS C:\Windows\system32> echo 'list disk' | diskpart
Microsoft DiskPart version 10.0.14393.0
Copyright (C) 1999-2013 Microsoft Corporation.
On computer: AHPAP2704
DISKPART>
Disk ### Status Size Free Dyn Gpt
-------- ------------- ------- ------- --- ---
Disk 0 Online 65 GB 0 B
Disk 1 Online 20 GB 0 B *
Disk 2 Offline 50 GB 0 B *
Ideally from the output above I'm going to set a variable to identify the dynamic volume (my script will always only have one) so when complete I'm left with something like $DynDisk = 2.
When I pipe the output to Get-Member the only member types containing property in the name are Chars and Length.
Is there an easy way to get the data into an array or a better method? Or, any chance there is some hidden grep and awk like cmdlets out there?
diskpart output isn't trimmed, so you can parse the relevant information from the end of the string, e.g. like this:
$re = 'disk (\d+)\s+(\w+)\s+(\d+ .?b)\s+(\d+ .?b) (.*)'
'list disk' | diskpart | Select-String -Pattern $re | ForEach-Object {
New-Object -Type PSObject -Property #{
ID = [int]$_.Matches.Groups[1].Value
Status = $_.Matches.Groups[2].Value -eq 'online'
Size = $_.Matches.Groups[3].Value
FreeSpace = $_.Matches.Groups[4].Value
Dynamic = $_.Matches.Groups[5].Value.Substring(0, 3).Trim() -eq '*'
GPT = $_.Matches.Groups[5].Value.Substring(4, 3).Trim() -eq '*'
}
}
All I needed was another half hour of googling.
https://gallery.technet.microsoft.com/DiskPartexe-Powershell-0f7a1bab
That script creates 3 objects which have properties I know now to handle

260 Character Limit, Get-ChildItem

I understand there is a 260 character limit for win32, but I am curious as to why my code is half working. See below.
$Age_of_Files = -30
$Path = '\\share\d$\share'
$Age_of_Files = -30
$Current_Date = Get-Date
$Del_date = $Current_Date.AddDays($Age_of_Files)
$post = "<BR><i>Report generated on $((Get-Date).ToString())</i>"
Get-ChildItem $Path -Recurse |
Where-Object { $_.LastWriteTime -lt $Del_date} |
Select Name, FullName, LastWriteTime
$Data | ConvertTo-HTML -PreContent $pre -PostContent $post | Out-File $Report
Invoke-Item $Report
Read-Host 'Have you checked the Output File...Ok to Continue with Delete?' | Out-Null
This will check my network share with no problem and give me no errors, although there are many directories longer then 260 characters but I also want to export this as a HTML file, so If I change this line of code.
$Data = Get-ChildItem $Path -Recurse |
Where-Object { $_.LastWriteTime -lt $Del_date } |
Select Name, FullName, LastWriteTime
It then does not recurse through the directories, and gives me the character limit error.
Is there a way around this? As apart from exporting it to HTML and adding in the actual delete command I think I am nearly there.
You can turn on long paths in Windows 10. There's a gpo.
https://www.howtogeek.com/266621/how-to-make-windows-10-accept-file-paths-over-260-characters/
This is a limitation of the Win32 API (see also). There's a PowerShell Module that supposedly works around the issue (haven't used it myself, though).
A common workaround is to shorten the path by substing the longest accessible path to a drive letter
& subst X: C:\longest\parent\folder
working on drive X:, then deleting the temporary drive afterwards:
& subst X: /d
For network paths use net use to the same end:
& net use X: \\server\share\longest\parent\folder
...
& net use X: /d