Author Posts

November 12, 2014 at 6:27 am

I've been banging my ahead away at this one since I'm still a beginner in my opinion. When I run the script it just says completed even thought defrag did not kick off.

1. I'd like to add a .txt file with machines to run this against. Thanks to anyone that can shed some light on what I'm doing wrong
2. I was also hoping to not just run this again the c drive I was hoping to run it on all drives
3. I've also tried running the script from within powershell and also from the PS prompt and still nothing...
4. I'm grateful for any help I can get.

function Start-Defrag {
[CmdletBinding(SupportsShouldProcess=$true)]
param ([string]$computer=".",
[string]$drive
)
if ($drive -notlike "c:"){ # anyway to do all drives?#
Throw "Drive should be submitted as letter and colon e.g. C:"}

$filt = "Name='" + $drive + "\\'"
$vol = Get-WmiObject -Class Win32_Volume -Filter $filt -ComputerName $computer
-ComputerName $computer

$res = $vol.Defrag($false)

if ($res.ReturnValue -eq 0) {
Write-Host "Defrag succeeded"
$res.DefragAnalysis |
Format-List AverageFileSize, AverageFragmentsPerFile,
AverageFreeSpacePerExtent, ClusterSize,
ExcessFolderFragments, FilePercentFragmentation,
FragmentedFolders, FreeSpace, FreeSpacePercent,
FreeSpacePercentFragmentation,
LargestFreeSpaceExtent, MFTPercentInUse,
MFTRecordCount, PageFileSize, TotalExcessFragments,
TotalFiles, TotalFolders, TotalFragmentedFiles,
TotalFreeSpaceExtents, TotalMFTFragments,
TotalMFTSize, TotalPageFileFragments,
TotalPercentFragmentation, TotalUnmovableFiles,
UsedSpace, VolumeName, VolumeSize
}
else {Write-Host "Defrag failed Result code: " $res.ReturnValue}
}

November 12, 2014 at 12:07 pm

I don't you are actually getting a volume back from your Get-WmiObject call because your filter isn't correct. Try this

$drive = 'C:'
$filt = "Name='$drive" + "\\'"
Get-WmiObject -Class Win32_Volume -Filter $filt

You need single quotes round the string you're using as a target.

November 12, 2014 at 1:58 pm

He's actually got them in there, Richard. They're just really hard to see...

I don't see any problem with that script, Pedro... How are you running it? Can you copy/paste exactly what you're typing into the console and what you're getting back?

November 13, 2014 at 6:30 am

Charles, I GREATLY APPRECIATE YOUR HELP BUD!!!

I've searched the entire WWW and I think a lot of PowerShell users would be happy to hopefully find this script if I can get it working right.

1. Thus far I added .txt file with machine names to run the script against
2. Added Login credentials to remotely execute this script again each machine
3. Question how can I get this script to run again not just the c drive just all drives on target machines?
4. I have the script in the Windows Power Shell ISE top half and results usally come out on the bottom half i.e. blue half.

Something is wrong with the array as the below is only running again my machine. I've check the target machines and the script is not getting there I checked the application, security and system logs.

$SCRIPT_PARENT = split-Path -Parent $MyInvocatoin.MyCommand.Definitatoin
$Servers = Get-content ($Script_Parent + "\hostnames.txt") -ErrorAction stop

$pass = convertTo-SecureString "mypassword" -AsPlaintText -Force
$user = New-Object Management.Automation.PSCredential ("myusername",$pass)
$drive = "c:"

for each ($comp in $Servers)
{

function Start-Defrag {
[CmdletBinding(SupportsShouldProcess=$true)]
param ([string[]]$computers)
If (!$computers) {$Computers = $Comp}
[string]$drive

if ($drive -notlike "c:"){ # anyway to do all drives?#
Throw "Drive should be submitted as letter and colon e.g. C:"}

$filt = "Name='" + $drive + "\\'"
$vol = Get-WmiObject -Class Win32_Volume -Filter $filt -ComputerName $computer -credential $user

$res = $vol.Defrag($false)

if ($res.ReturnValue -eq 0) {
Write-Host "Defrag succeeded"
$res.DefragAnalysis |
Format-List AverageFileSize, AverageFragmentsPerFile,
AverageFreeSpacePerExtent, ClusterSize,
ExcessFolderFragments, FilePercentFragmentation,
FragmentedFolders, FreeSpace, FreeSpacePercent,
FreeSpacePercentFragmentation,
LargestFreeSpaceExtent, MFTPercentInUse,
MFTRecordCount, PageFileSize, TotalExcessFragments,
TotalFiles, TotalFolders, TotalFragmentedFiles,
TotalFreeSpaceExtents, TotalMFTFragments,
TotalMFTSize, TotalPageFileFragments,
TotalPercentFragmentation, TotalUnmovableFiles,
UsedSpace, VolumeName, VolumeSize
}
else {Write-Host "Defrag failed Result code: " $res.ReturnValue}
}
}

November 13, 2014 at 7:02 am

So, several things going on here...

  1. First, you've defined your function inside your foreach loop. The function should be defined before the loop and then called within the loop.
  2. Second, if you want to pull in a list of all logical drives on a system, you can use Get-WmiObject on the Win32_Volume class
    # Get all logical volumes for $ComputerName
    			$drives = Get-WmiObject win32_volume -ComputerName $ComputerName | 
    				Where-Object { $_.DriveType -eq 3 -and $_.DriveLetter -ne $null}
  3. You've got the right idea using a loop to get the script to run against multiple machines, but you are never calling the function inside the loop. See first item above. 🙂
  4. In this case, I'd change your $computers parameter in your Start-Defrag function to be a single value instead of an array. Another, (maybe better but still more advanced), option would be to convert it to an advanced function and use BEGIN, PROCESS, END blocks.
  5. Lastly, search the TechNet Gallery for scripts that have already been written to do this. I've even got an old one out there, though I'm not sure I'd recommend it any more...

I haven't tested this, so there are probably some flaws in it, but maybe this will get you a little further along:

function Start-Defrag
{
	[CmdletBinding(SupportsShouldProcess = $true)]
	param (
		[string]$ComputerName,
		[string]$drive
	)
	
	# If a drive was specified, only run defrag on that drive. If no drive was specified, defrag all drives.
	if (!$drive)
	{
		# Get all logical volumes for $ComputerName
		$drives = Get-WmiObject win32_volume -ComputerName $ComputerName | Where-Object { $_.DriveType -eq 3 -and $_.DriveLetter -ne $null }
	}
	else
	{
		# Make an empty array and then add the passed-in drive to the array
		$drives = @()
		$drives += $drive
	}
	
	$drives | ForEach-Object {
		$res = $_.Defrag($false)
		
		if ($res.ReturnValue -eq 0)
		{
			Write-Host "Defrag succeeded"
			$res.DefragAnalysis |
			Format-List AverageFileSize, AverageFragmentsPerFile,
						AverageFreeSpacePerExtent, ClusterSize,
						ExcessFolderFragments, FilePercentFragmentation,
						FragmentedFolders, FreeSpace, FreeSpacePercent,
						FreeSpacePercentFragmentation,
						LargestFreeSpaceExtent, MFTPercentInUse,
						MFTRecordCount, PageFileSize, TotalExcessFragments,
						TotalFiles, TotalFolders, TotalFragmentedFiles,
						TotalFreeSpaceExtents, TotalMFTFragments,
						TotalMFTSize, TotalPageFileFragments,
						TotalPercentFragmentation, TotalUnmovableFiles,
						UsedSpace, VolumeName, VolumeSize
		}
		else { Write-Host "Defrag failed Result code: " $res.ReturnValue }
	}
}

$SCRIPT_PARENT = split-Path -Parent $MyInvocatoin.MyCommand.Definitatoin
$Servers = Get-content ($Script_Parent + "\hostnames.txt") -ErrorAction stop

$pass = convertTo-SecureString "mypassword" -AsPlaintText -Force
$user = New-Object Management.Automation.PSCredential ("myusername", $pass)
$drive = "c:"

foreach ($comp in $Servers)
{
	Start-Defrag -ComputerName $comp
}

November 13, 2014 at 8:18 am

Charles

I fixed some of the issues I found in the script – when executing the script the see the below

1. good things credentials are now logging into the remote computer
2. But defrag is not occurring. 🙁

Below is the updated script

function Start-Defrag
{
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[string]$Comp,
[string]$drive
)

# If a drive was specified, only run defrag on that drive. If no drive was specified, defrag all drives.
if (!$drive)
{
$drives = Get-WmiObject win32_volume -ComputerName $Comp | Where-Object { $_.Drive
}
else
{
# Make an empty array and then add the passed-in drive to the array
$drives = @()
$drives += $drive
}
$drives | ForEach-Object {
$res = $_.Defrag($false)

if ($res.ReturnValue -eq 0)
{
Write-Host "Defrag succeeded"
$res.DefragAnalysis |
Format-List AverageFileSize, AverageFragmentsPerFile,
AverageFreeSpacePerExtent, ClusterSize,
ExcessFolderFragments, FilePercentFragmentation,
FragmentedFolders, FreeSpace, FreeSpacePercent,
FreeSpacePercentFragmentation,
LargestFreeSpaceExtent, MFTPercentInUse,
MFTRecordCount, PageFileSize, TotalExcessFragments,
TotalFiles, TotalFolders, TotalFragmentedFiles,
TotalFreeSpaceExtents, TotalMFTFragments,
TotalMFTSize, TotalPageFileFragments,
TotalPercentFragmentation, TotalUnmovableFiles,
UsedSpace, VolumeName, VolumeSize
}
else { Write-Host "Defrag failed Result code: " $res.ReturnValue }
}
}

$SCRIPT_PARENT = split-Path -Parent $MyInvocatoin.MyCommand.Definitatoin
$Servers = Get-content ($Script_Parent + "\hostnames.txt") -ErrorAction stop

$pass = convertTo-SecureString "mypassword" -AsPlaintText -Force
$user = New-Object Management.Automation.PSCredential ("myusername", $pass)
$drive = "c:"

foreach ($comp in $Servers)
{
Start-Defrag -ComputerName $comp
}
}

November 13, 2014 at 8:38 am

Charles I got the script working.

my apologies it was a type on my end.

November 13, 2014 at 11:57 am

Glad it's working, man! Good luck!

November 14, 2014 at 5:35 am

Hey Charles – I found one problem with the final script.

I ended up exporting to csv instead out output to the screen using format-list.

When I open up the csv file, everything is nicely layed out except that I don't have the computer name listed at all, is there any way for me to grab the $comp name?

thanks ALOT!!!

Function Start-Defrag
{
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[string]$Comp,
[string]$drive
)

# If a drive was specified, only run defrag on that drive. If no drive was specified, defrag all drives.
if (!$drive)
{
$drives = Get-WmiObject win32_volume -ComputerName $Comp | Where-Object { $_.DriveType -eq 3 and $_.DriveLetter -ne $null }
}
else
{
# Make an empty array and then add the passed-in drive to the array
$drives = @()
$drives += $drive
}
$drives | ForEach-Object {
$res = $_.Defrag($false)

if ($res.ReturnValue -eq 0)
{
Write-Host "Defrag succeeded"
$res.DefragAnalysisTotalExcessFragments,
}
else { Write-Host "Defrag failed Result code: " $res.ReturnValue }
}
}

$SCRIPT_PARENT = split-Path -Parent $MyInvocatoin.MyCommand.Definitatoin
$Servers = Get-content ($Script_Parent + "\hostnames.txt") -ErrorAction stop
$pass = convertTo-SecureString "mypassword" -AsPlaintText -Force
$user = New-Object Management.Automation.PSCredential ("myusername", $pass)
$drive = "c:"
$filename = ("c:\reports\defrag-report.csv")

foreach ($comp in $Servers)
{
Start-Defrag -ComputerName $comp |
Select $Comp TotalFiles, TotalFolders, TotalFragmentedFiles,
TotalFreeSpaceExtents, TotalMFTFragments,
TotalMFTSize, TotalPageFileFragments,
TotalPercentFragmentation, TotalUnmovableFiles,
sort-objEect $Comp |Export-csv – path $filename
}

November 14, 2014 at 7:08 am

Sure! In your Select-Object statement, you'll need to use a calculated property. Something like:

Start-Defrag -ComputerName $comp |
	Select-Object @{ Name = "ComputerName"; Expression = { $Comp } },
				  TotalFiles,
				  TotalFolders,
				  TotalFragmentedFiles,
				  TotalFreeSpaceExtents,
				  TotalMFTFragments,
				  TotalMFTSize,
				  TotalPageFileFragments,
				  TotalPercentFragmentation,
				  TotalUnmovableFiles |
	Sort-Object ComputerName |
	Export-csv – path $filename

November 18, 2014 at 12:49 pm

Hey Charles I found one small bug and I didn't want to write you asking for help again and again.

when I export to csv only the last hostname in kept in the csv file. for example 5 hostnames in the .txt file and only the last hostname is in the csv file in line 1. its like row 1 gets overwritten by the next hostname.

I even tried moving the select statement up into the function but still same issue. Do you have any ideas?

THANKS BUD YOU ROCK!!! I HOPE TO GET BETTER AND BETTER AT PS!!!

November 19, 2014 at 7:16 am

That's actually exactly what's going on! I didn't think about that when I responded the last time. The way you have it set up, you're overwriting your CSV for each server that is processed. You need to store the results of each Start-Defrag pipeline as a new element in an array and then after the loop is finished, export that array to a CSV.