How to Write to Log-File Without Writing to Console - powershell

New to PowerShell.
Some experience with Linux, bash, and programming (Java, C, HTML/CSS/JS). I just started an internship.
I was given a PowerShell script in order to do basic disk clean-up. Part of it pasted below. It writes to both console and a logfile. Some of the servers that I am cleaning having hundreds of thousands of files. I want to increase the performance of the script by only writing to the logfile. It usually starts out pretty strong, but once the console output gets large enough, things start to slow down drastically.
I attempted to simply remove the -verbose tags, but then it doesn't write to either. Then my understanding was that 'SilentlyContinue' would allow print to log, but not console. But the code already has SilentlyContinue flags? I then tried adding-Verbose to some of the for-each statements and that didn't work either.
I'm just kind of running in circles now.
Any ideas or pointers?
function global:Write-Verbose
(
[string]$Message
)
{ # check $VerbosePreference variable
if ( $VerbosePreference -ne 'SilentlyContinue' )
{ Write-Host " $Message" -ForegroundColor 'Yellow' }
}
Write-Verbose
$DaysToDelete = 7
$LogDate = get-date -format "MM-d-yy-HH"
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.Namespace(0xA)
Start-Transcript -Path C:\Windows\Temp\$LogDate.log
​
#Cleans all code off of the screen.
Clear-Host
​
$Before = Get-WmiObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq "3" } | Select-Object SystemName,
#{ Name = "Drive" ; Expression = { ( $_.DeviceID ) } },
#{ Name = "Size (GB)" ; Expression = { "{0:N1}" -f ( $_.Size / 1gb) } },
#{ Name = "FreeSpace (GB)" ; Expression = { "{0:N1}" -f ( $_.Freespace / 1gb ) } },
#{ Name = "PercentFree" ; Expression = { "{0:P1}" -f ( $_.FreeSpace / $_.Size ) } } |
Format-Table -AutoSize | Out-String
## Stops the windows update service.
Get-Service -Name wuauserv | Stop-Service -Force -Verbose -ErrorAction SilentlyContinue
## Windows Update Service has been stopped successfully!
​
## Deletes the contents of windows software distribution.
Get-ChildItem "C:\Windows\SoftwareDistribution\*" -Recurse -Force -Verbose -ErrorAction SilentlyContinue |
Where-Object { ($_.CreationTime -lt $(Get-Date).AddDays(-$DaysToDelete)) } |
remove-item -force -Verbose -recurse -ErrorAction SilentlyContinue
## The Contents of Windows SoftwareDistribution have been removed successfully!
​
## Deletes the contents of the Windows Temp folder.
Get-ChildItem "C:\Windows\Temp\*" -Recurse -Force -Verbose -ErrorAction SilentlyContinue |
Where-Object { ($_.CreationTime -lt $(Get-Date).AddDays(-$DaysToDelete)) } |
remove-item -force -Verbose -recurse -ErrorAction SilentlyContinue
## The Contents of Windows Temp have been removed successfully!
​
## Deletes all files and folders in user's Temp folder.
Get-ChildItem "C:\users\$env:USERNAME\AppData\Local\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { ($_.CreationTime -lt $(Get-Date).AddDays(-$DaysToDelete)) } |
remove-item -force -Verbose -recurse -ErrorAction SilentlyContinue
## The contents of C:\users\$env:USERNAME\AppData\Local\Temp\ have been removed successfully!
## Remove all files and folders in user's Temporary Internet Files.
Get-ChildItem "C:\users\$env:USERNAME\AppData\Local\Microsoft\Windows\Temporary Internet Files\*" -Recurse -Force -Verbose -ErrorAction SilentlyContinue |
Where-Object { ($_.CreationTime -le $(Get-Date).AddDays(-$DaysToDelete)) } |
remove-item -force -recurse -ErrorAction SilentlyContinue
## All Temporary Internet Files have been removed successfully!
## Cleans IIS Logs if applicable.
Get-ChildItem "C:\inetpub\logs\LogFiles\*" -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { ($_.CreationTime -le $(Get-Date).AddDays(-60)) } |
Remove-Item -Force -Verbose -Recurse -ErrorAction SilentlyContinue
## All IIS Logfiles over x days old have been removed Successfully!
## deletes the contents of the recycling Bin.
$objFolder.items() | ForEach-Object { Remove-Item $_.path -ErrorAction Ignore -Force -Verbose -Recurse }
## The Recycling Bin has been emptied!
## Starts the Windows Update Service
Get-Service -Name wuauserv | Start-Service -Verbose
​
$After = Get-WmiObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq "3" } | Select-Object SystemName,
#{ Name = "Drive" ; Expression = { ( $_.DeviceID ) } },
#{ Name = "Size (GB)" ; Expression = { "{0:N1}" -f ( $_.Size / 1gb) } },
#{ Name = "FreeSpace (GB)" ; Expression = { "{0:N1}" -f ( $_.Freespace / 1gb ) } },
#{ Name = "PercentFree" ; Expression = { "{0:P1}" -f ( $_.FreeSpace / $_.Size ) } } |
Format-Table -AutoSize | Out-String
​
## Sends some before and after info for ticketing purposes
Hostname ; Get-Date | Select-Object DateTime
Write-Host "Before: $Before"
Write-Host "After: $After"
​
Write-Verbose ( Get-ChildItem -Path C:\* -Include *.iso, *.vhd, *.vhdx -Recurse -ErrorAction SilentlyContinue |
Sort Length -Descending | Select-Object Name, Directory,
#{Name = "Size (GB)"; Expression = { "{0:N2}" -f ($_.Length / 1GB) } } | Format-Table |
Out-String )
​
## Completed Successfully!
Stop-Transcript

You want to look into Redirection: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection?view=powershell-7.1
It's useful for logging your Catch output, wrapping it in ${} and specifying your stream output. Ex: ${ Write-Verbose ... } 4>&1 3>&1 2>&1 >> $logFile
Most of what you have though looks like you're trying to log data for reference. Like your Before:After & final Get-ChildItem statements. If you're doing it as you go, you can pipe to Out-File -Append to a log file. But since you have it in a block towards the end, you can simply wrap & redirect:
&{
Write-Host "Before: " $Before
Write-Host "After: " $After"
Get-ChildItem -Path C:\* -Include *.iso, *.vhd, *.vhdx -Recurse -ErrorAction SilentlyContinue |
Sort Length -Descending | Select-Object Name, Directory,
#{Name = "Size (GB)"; Expression = { "{0:N2}" -f ($_.Length / 1GB) } } | Format-Table |
Out-String
} *> C:\FilePath\File.txt
Notice you don't even need to wrap your Get-ChildItem in a Write-Verbose statement. Get cmdlets that spit out to console, will just write the output to the file when using redirection. The only time you should need to issue a Write statement is if you are adding a string of text / interpolating data.
On an unrelated note, I see a DRY opportunity. Your before & after statements are identical. Put a function towards the top of your file that returns the statement, that way you can just assign your $Before & $After vars to the return output.

Related

Out-File Of ForEach With If(Test-Path ...)

$homefolder = (gci \\SERVER\homefolder | select fullname)
$outfile = "$env:USERPROFILE\Desktop\Homefolder_Desktop_Redirect.csv"
ForEach ($dir in $homefolder)
{If(Test-Path ($dir.FullName +"\Desktop")){write-host $dir.Fullname" contains desktop" -ForegroundColor Yellow
"{0:N2} GB" -f ((Get-ChildItem $dir.fullname -Recurse | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum / 1GB)
}}
ForEach ($dir in $homefolder)
{If(Test-Path ($dir.FullName +"\Desktop")){}else{write-host $dir.Fullname" does not contain desktop" -ForegroundColor Red
"{0:N2} GB" -f ((Get-ChildItem $dir.fullname -Recurse | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum / 1GB)
}}
I'm trying to get this to output to a file. If I put the pipe between the last 2 }} or after the last } (in each Foreach), I'm told it's empty. If I put IF inside another set of parentheses, like {(If I get If isn't valid.
If I try to write/append after 1GB) my outfile is just my script.
If I try making the Foreach($dir in $homefolder) a variable, the in is an unexpected token.
I'm sure this is something simple, but I haven't used PowerShell for much in the last 5 years... assistance is appreciated.
---UPDATE---
Thanks for the help, all!
This is what I have thanks to the assistance I've received.
$outfile = "$env:USERPROFILE\Desktop\Homefolder_Desktop_Redirect.txt"
Write-Output "Contains desktop:" | Set-Content $outfile -Force
(Get-ChildItem \\SERVER\homefolder).FullName | ForEach-Object {
if(Test-Path (Join-Path $_ -ChildPath Desktop)) {
Write-Host "$_ contains desktop" -ForegroundColor Yellow
"$_ [{0:N2} GB]" -f (
(Get-ChildItem $_ -Recurse |
Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue
).Sum / 1GB)
}
} | Add-Content $outfile -Force
Write-Output "Contains NO desktop:" | Add-Content $outfile -Force
(Get-ChildItem \\SERVER\homefolder).FullName | ForEach-Object {
if(Test-Path (Join-Path $_ -ChildPath Desktop)) {}
else{
Write-Host "$_ contains no desktop" -ForegroundColor Red
"$_ [{0:N2} GB]" -f (
(Get-ChildItem $_ -Recurse |
Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue
).Sum / 1GB)
}
} | Add-Content $outfile -Force
Invoke-Item $outfile
The main reason why PowerShell complains is because you're looking to pipe after a language keyword which is simply not possible. You can however, use ForEach-Object, a cmdlet designed to enumerate input objects from pipeline, and because it is a cmdlet and not a statement (foreach), you can pipe other cmdlets to it:
(Get-ChildItem \\SERVER\homefolder).FullName | ForEach-Object {
if(Test-Path (Join-Path $_ -ChildPath Desktop)) {
Write-Host "$_ contains desktop" -ForegroundColor Yellow
}
else {
Write-Host "$_ does not contain desktop" -ForegroundColor Red
}
$output = "$_ [{0:N2} GB]" -f (
(Get-ChildItem $_ -Recurse |
Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue
).Sum / 1GB)
# send output to the host
Write-Host $output
# send output to the success stream
$output
} | Set-Content path\to\export.txt
Generally, if you want to send the output from multiple statements to a single file, enclose them in & { ... } (or . { ... } to run directly in the caller's scope); a simplified example:
& {
foreach ($i in 1..5) { $i }
foreach ($i in 6..10) { $i }
} | Out-File test.txt
However, you can reformulate your code to a single pipeline, using the ForEach-Object cmdlet rather than the foreach loop statement:
$homefolder |
ForEach-Object {
$hasDesktop = Test-Path (Join-Path $_.FullName Desktop)
Write-Host ('{0} {1} desktop' -f $_.FullName, ('does not contain', 'contains')[$hasDesktop]) -ForegroundColor ('Red', 'Yellow')[$hasDesktop]
'Contains {0}desktop' -f ('NO ', '')[$hasDesktop]
'{0:N2} GB' -f ((Get-ChildItem $_.FullName -Recurse | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum / 1GB)
} |
Out-File $outfile
Note:
Note the technique of letting a Boolean variable $hasDesktop select one of two values from an array, e.g., ('Red', 'Yellow')[$hasDesktop], which allows you to make do with a single Write-Host call to cover both the has-desktop and doesn't-have-desktop case.
This acts similar to the ternary conditional operator, available in PowerShell (Core) 7+ only; that is, the above is equivalent to: ($hasDesktop ? 'Yellow' : 'Red')
The output string is pieced together with the help of -f, the format operator.
As for your desire to print the calculated size to the display as well:
In Windows PowerShell, capture it in a variable first, write its value to the display with Write-Host, then output it to the success stream, as shown in Santiago Squarzon's helpful answer.
In PowerShell (Core) 7+, you can simply pipe to Tee-Object CON, as discussed in this answer.

How to optimize Powershell script with Get-ChildItem consuming all RAM

I have this script which parses all shares on a file server to gather information on share size, ACLs, and count of files and folders. The script works great on smaller file servers but on hosts with large shares it consumes all RAM and crashes the host, I can't seem to figure out how to optimize the script during the Get-ChildItem portion to not consume all RAM.
I found a few articles which mentioned to use a foreach loop and pipe out what I need. I am a Powershell beginner, I can't figure out how to get it to work like that. What can I try next?
$ScopeName = Read-Host "Enter scope name to gather data on"
$SavePath = Read-Host "Path to save results and log to"
$SaveCSVPath = "$SavePath\ShareData.csv"
$TranscriptLog = "$SavePath\Transcript.log"
Write-Host
Start-Transcript -Path $TranscriptLog
$StartTime = Get-Date
$Start = $StartTime | Select-Object -ExpandProperty DateTime
$Exclusions = {$_.Description -ne "Remote Admin" -and $_.Description -ne "Default Share" -and $_.Description -ne "Remote IPC" }
$FileShares = Get-SmbShare -ScopeName $ScopeName | Where-Object $Exclusions
$Count = $FileShares.Count
Write-Host
Write-Host "Gathering data for $Count shares" -ForegroundColor Green
Write-Host
Write-Host "Results will be saved to $SaveCSVPath" -ForegroundColor Green
Write-Host
ForEach ($FileShare in $FileShares)
{
$ShareName = $FileShare.Name
$Path = $Fileshare.Path
Write-Host "Working on: $ShareName - $Path" -ForegroundColor Yellow
$GetObjectInfo = Get-Childitem -Path $Path -Recurse -Force -ErrorAction SilentlyContinue
$ObjSize = $GetObjectInfo | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue
$ObjectSizeMB = "{0:N2}" -f ($ObjSize.Sum / 1MB)
$ObjectSizeGB = "{0:N2}" -f ($ObjSize.Sum / 1GB)
$ObjectSizeTB = "{0:N2}" -f ($ObjSize.Sum / 1TB)
$NumFiles = ($GetObjectInfo | Where-Object {-not $_.PSIsContainer}).Count
$NumFolders = ($GetObjectInfo | Where-Object {$_.PSIsContainer}).Count
$ACL = Get-Acl -Path $Path
$LastAccessTime = Get-ItemProperty $Path | Select-Object -ExpandProperty LastAccessTime
$LastWriteTime = Get-ItemProperty $Path | Select-Object -ExpandProperty LastWriteTime
$Table = [PSCustomObject]#{
'ScopeName' = $FileShare.ScopeName
'Sharename' = $ShareName
'SharePath' = $Path
'Owner' = $ACL.Owner
'Permissions' = $ACL.AccessToString
'LastAccess' = $LastAccessTime
'LastWrite' = $LastWriteTime
'Size (MB)' = $ObjectSizeMB
'Size (GB)' = $ObjectSizeGB
'Size (TB)' = $ObjectSizeTB
'Total File Count' = $NumFiles
'Total Folder Count' = $NumFolders
'Total Item Count' = $GetObjectInfo.Count
}
$Table | Export-CSV -Path $SaveCSVPath -Append -NoTypeInformation
}
$EndTime = Get-Date
$End = $EndTime | Select-Object -ExpandProperty DateTime
Write-Host
Write-Host "Script start time: $Start" -ForegroundColor Green
Write-Host "Script end time: $End" -ForegroundColor Green
Write-Host
$ElapsedTime = $(($EndTime-$StartTime))
Write-Host "Elapsed time: $($ElapsedTime.Days) Days $($ElapsedTime.Hours) Hours $($ElapsedTime.Minutes) Minutes $($ElapsedTime.Seconds) Seconds $($ElapsedTime.MilliSeconds) Milliseconds" -ForegroundColor Cyan
Write-Host
Write-Host "Results saved to $SaveCSVPath" -ForegroundColor Green
Write-Host
Write-Host "Transcript saved to $TranscriptLog" -ForegroundColor Green
Write-Host
Stop-Transcript
To correctly use the PowerShell pipeline (and preserve memory as each item is streamed separately), use the PowerShell ForEach-Object cmdlet (unlike the ForEach statement) and avoid assigning the pipeline to a variable (as you doing with $FileShares = ...) and don't use parenthesis ((...)) arround the the pipeline:
Get-SmbShare -ScopeName $ScopeName | Where-Object $Exclusions | ForEach-Object {
And replace all $FileShare variables in your loop with the current item: $_ variable (e.g. $FileShare.Name → $_.Name).
For the Get-Childitem part you might do the same thing (stream! meaning: use the mighty PowerShell pipeline rather than piling everything up in $GetObjectInfo):
$ObjSize = Get-Childitem -Path $Path -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue
As an aside; you might simplify your 3 size properties to a single smarter size property, see: How to convert value to KB, MB, or GB depending on digit placeholders?
addition
"But isn't putting everything into $ObjSize just swapping one variable for another?"
No it is not, think of the PowerShell pipeline as an assembly line. In this case, at the first station you take each single file information and pass it to the next (last) station where you just sum the length property and the current (file) object disposed.
Where in your question example, you read the information of all files in once and store it into $GetObjectInfo and than go to the whole list to just use (add) the length property of the (quiet heavy) PowerShell file objects.
But why don't you try it?:
Open a new PowerShell session and run:
$Path = '.'
$GetObjectInfo = Get-Childitem -Path $Path -Recurse -Force -ErrorAction SilentlyContinue
$ObjSize = $GetObjectInfo | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue
Get-Process -ID $PID
Now, open a new session again and use the PowerShell pipeline:
$Path = '.'
$ObjSize = Get-Childitem -Path $Path -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue
Get-Process -ID $PID
Notice the difference in memory usage (WS(M)).
You are buffering the entire collection of [FileSystemInfo] on $FileShare into a variable with...
$GetObjectInfo = Get-Childitem -Path $Path -Recurse -Force -ErrorAction SilentlyContinue
So, if there's a million directories and files on that share then that's a million [FileSystemInfo] instances stored in a million-element array, none of which can be garbage collected during that iteration of the foreach loop. You can use Group-Object to improve that a bit...
$groupsByPSIsContainer = Get-Childitem -Path $Path -Recurse -Force -ErrorAction SilentlyContinue |
Group-Object -Property 'PSIsContainer' -AsHashTable
# $groupsByPSIsContainer is a [Hashtable] with two keys:
# - $true gets the collection of directories
# - $false gets the collection of files
$ObjSize = $groupsByPSIsContainer[$false] | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue
$NumFiles = $groupsByPSIsContainer[$false].Count
$NumFolders = $groupsByPSIsContainer[$true].Count
...but that still ends up storing all of the [FileSystemInfo]s in the two branches of the [Hashtable]. Instead, I would just enumerate and count the results myself...
$ObjSize = 0L # Stores the total file size directly; use $ObjSize instead of $ObjSize.Sum
$NumFiles = 0
$NumFolders = 0
foreach ($fileSystemInfo in Get-Childitem -Path $Path -Recurse -Force -ErrorAction SilentlyContinue)
{
if ($fileSystemInfo.PSIsContainer)
{
$NumFolders++
}
else
{
$NumFiles++
$ObjSize += $fileSystemInfo.Length
}
}
That stores only the current enumeration result in $fileSystemInfo and never the entire sequence.
Note that if you weren't summing the files' sizes Group-Object would work well...
$groupsByIsContainer = Get-Childitem -Path $Path -Recurse -Force -ErrorAction SilentlyContinue |
Group-Object -Property 'PSIsContainer' -NoElement
$NumFiles = ($groupsByIsContainer | Where-Object -Property 'Name' -EQ -Value $false).Count
$NumFolders = ($groupsByIsContainer | Where-Object -Property 'Name' -EQ -Value $true ).Count
-NoElement prevents the resulting group objects from storing the grouped elements; we just care about the count of members in each grouping but not the members themselves. If we passed -AsHashTable then we'd lose the convenient Count property, hence why the two groups have to be accessed in this awkward way.

User Profile Names and Sizes into CSV File

This script gets user profiles on a machine and I am able to provide the output into a text file but I cannot figure out how to export this information to a CSV file.
dir C:\Users | foreach -Begin {} -Process {
$size = (dir $_.FullName -Recurse -Force -EA SilentlyContinue | Measure-Object ‘length’ -Sum -Maximum).Sum
Write-Output ("{0:n2}" -f ($size/1MB) + " MB", $_.FullName) >> "C:\scripts\UserProfiles\UserProfiles.txt"
}
You'll need to create an object with properties and then use Export-Csv
Get-ChildItem C:\Users |
ForEach-Object `
-Begin { Write-Host -Object "Scanning user directories" } `
-Process {
Write-Host "Scanning path '$($_.FullName)'"
$Size = (Get-ChildItem $_.FullName -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object ‘length’ -Sum -Maximum).Sum
[pscustomobject] #{
Name = $_.Name
Path = $_.FullName
Size = '{0:N2} MB' -f ( $Size / 1MB )
}
} |
Export-Csv -Path C:\Scripts\UserProfiles\UserProfiles.csv -NoTypeInformation

Powershell script launched from logon script not working

I'm trying to create a powershell script that searches a users C drive for a certain file extension, and then writes a file to a network share if it finds one. The script is launched from the last line of a logon script that reads like this:
powershell.exe -ExecutionPolicy Bypass -windowstyle hidden -file "\\servername\Path\To\File.ps1"
And my powershell script looks like this:
$hostname = HostName
Get-ChildItem -Path C:\*.* -Filter $file -Recurse -Force -ErrorAction
SilentlyContinue | Out-File \\servername\Path\To\Results\$hostname\file.txt
If ((Get-Content "\\servername\Path\To\Results\$hostname\file.txt") -eq $Null) {
Remove-Item \\servername\Path\To\Results\$hostname\file.txt
}
Exit
The script runs perfectly fine on my machine even when I load it from the network share but whenever another computer runs it, it never produces an Out File. And I don't think it is even searching.
What am I doing wrong? I thought it was execution policy, but I've set the logon script to bypass it. I don't understand why it isn't working.
[edit]
I've now got the script working sometimes on Windows 10 machines. But it doesn't work at all on Windows 7. I've even tried running
Get-ChildItem C:\*.pst -Recurse
directly from a powershell command prompt, and it just fails silently and doesn't search for anything. Isn't Get-ChildItem a powershell 1 command?
Hello. If you do like this: Get-ChildItem -Path C:*.* -Filter $file
-Recurse -Force the text file output will be enough to weigh.
You can try to check the access to the network folder for the current
user:if access explicitly set, and write access exists, then you can
record a file with the content. Otherwise, it can create folder test
on the local machine which will create the file, indicating that there
is no access. How is this way:
Set-ExecutionPolicy remotesigned -Scope CurrentUser -Force| Out-Null
$user = [System.Environment]::UserName
$hostname = [System.Environment]::MachineName
try {
$accs = Get-ACL -Path "\\server\sharedfolder\Results\"
foreach ($access in $accs) {
$obj = New-Object -TypeName PSObject -Property #{
Access = $accs.Access
}
$obj1 = $obj | select -ExpandProperty Access
for ($i = 0 ; $i -le $obj1.Count ; $i ++ )
{
if (!($obj1[$i].IdentityReference -like "*Users*" -or $obj1[$i].IdentityReference -like "*$user*")) {
if (!(Test-Path "c:\test")) {
md c:\test
$s = "user access from group"
$s | out-file C:\test\ErrInfo.csv
}
else {
$s = "user access from group"
$s | out-file C:\test\ErrInfo.csv
}
}
if ($obj1[$i].IdentityReference -like "*Users*" -or $obj1[$i].IdentityReference -like "*$user*") {
if ($obj1[$i].FileSystemRights -like "*ReadAndExecute*")
{
if (!(Test-Path "c:\test")) {
md c:\test
$s = "Premission only ReadAndExecute"
$s | out-file C:\test\ErrInfo_rex.csv
}
else {
$s = "Premission only ReadAndExecute"
$s | out-file C:\test\ErrInfo_rex.csv
}
}
if ($obj1[$i].FileSystemRights -like "*FullControl*" -and $obj1[$i].AccessControlType -like "*Allow*" -or $obj1[$i].FileSystemRights -like "*Modify*" -and $obj1[$i].AccessControlType -like "*Allow*")
{
if (!(Test-Path "\\server\sharedfolder\Results\$hostname"))
{
md "\\server\sharedfolder\Results\$hostname"
Get-ChildItem -Path C:\testpatch\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue | Out-File "\\server\sharedfolder\Results\$hostname\file.txt"
}
else {
Get-ChildItem -Path C:\testpatch\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue | Out-File "\\server\sharedfolder\Results\$hostname\file.txt"
}
}
}
}
}
}
catch {
if (!(Test-Path "c:\test")) {
md c:\test
$s = "--NoAccess--"
$s | out-file C:\test\ErrInfo_noaccess.csv
}
else {
$s = "--NoAccess--"
$s | out-file C:\test\ErrInfo_noaccess.csv
}
}
Or you can do something like this (whiteout EXIT):
Set-ExecutionPolicy remotesigned -Scope CurrentUser -Force| Out-Null
$hostname = [System.Environment]::MachineName
if (!(Test-Path "\\server\sharedfolder\Results\$hostname")) {
md "\\server\sharedfolder\Results\$hostname" | Out-Null
If ((Get-ChildItem -Path C:\test\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue).Count -ne "0") {
Get-ChildItem -Path C:\test\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue | Out-File "\\server\sharedfolder\Results\$hostname\file.txt"
}
}
elseif (Test-Path "\\server\sharedfolder\Results\$hostname") {
If ((Get-ChildItem -Path C:\test\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue).Count -ne "0") {
Get-ChildItem -Path C:\test\*.* -Filter $file -Recurse -Force -ErrorAction SilentlyContinue | Out-File "\\server\sharedfolder\Results\$hostname\file.txt"
}
}

PowerShell Default value not working

I'm trying to feed my function with some variables and when no variable is given it should use a default value of 30 for OlderThanDays. For one reason or another this is not working out as I'd expected.
I fixed my problem in the ForEach loop by using if($_.B -ne $null) {$OlderThanDays=$_.B} else {$OlderThanDays="30"} But I don't think this is best practice. Can anyone tell me why [Int]$OlderThanDays=30 isn't working?
Problem: When adding a line in my csv-file without defining the OlderThanDaysvariable, the default of 30 days is not used and the files are just deleted...
Thank you for your help.
CSV file:
# Correct input formats are:
#
# ServerName, LocalPath, OlderThanDays
# Ex: server, E:\SHARE\Target, 10
# Ex: server, E:\CBR\SHARE\Target
#
# UNC-Path, OlderThanDays
# Ex: \\domain\SHARE\Target, 20
# Ex: \\domain\Target
#
# If no 'OlderThanDays' is provided, a default of 30 days will be used
# Commenting out can be done with '#'
# ______________________________________________________________________
SERVER1, E:\SHARE\Target
\\domain\SHARE\Target2
Full script:
#__________________________________________________________________________________________________________________________________
$ImportFile = "S:\Input\Scheduled Task\Auto_Clean.csv"
$Password = cat "S:\Input\pwd.txt" | ConvertTo-SecureString -Force
$UserName = "domain\me"
#__________________________________________________________________________________________________________________________________
# Scriptblock for running the function in a job
$JobCall = {
# Function that removes files older than x days in all subfolders
Function Delete-OldFiles {
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[Parameter(Mandatory=$True,Position=1)]
[ValidateScript({Test-Path $_})]
[String]$Target,
[Parameter(Mandatory=$False,Position=2)]
[Int]$OlderThanDays=30,
[Parameter(Mandatory=$False,Position=3)]
[String]$Server,
[switch]$CleanFolders
)
#__________________________________________________________________________________________________________________________________
# Create logfile
$TempDate = (get-date).ToString("dd-MM-yyyy")
$TempFolderPath = $Target -replace '\\','_'
$TempFolderPath = $TempFolderPath -replace ':',''
$TempFolderPath = $TempFolderPath -replace ' ',''
$script:LogFile = "\\DEUSTHEIDIT02\Log\Scheduled Task\Auto_Clean\$Server - $TempFolderPath - $TempDate.log"
#__________________________________________________________________________________________________________________________________
# Check the version of PowerShell
if ($PSVersionTable.PSVersion.Major -ge "3") {
# PowerShell 3+ Remove files older than (FASTER)
Get-ChildItem -Path $Target -Recurse -File |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
"$Timestamp | REMOVED: $Server $Item"
}
} | Tee-Object $LogFile -Append -Verbose}
Else {
# PowerShell 2 Remove files older than
Get-ChildItem -Path $Target -Recurse |
Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
Write-Host "$Timestamp | FAILLED: $Server $Item (IN USE)"
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
Write-Host "$Timestamp | REMOVED: $Server $Item"
"$Timestamp | REMOVED: $Server $Item"
}
} | Out-File $LogFile -Append }
#__________________________________________________________________________________________________________________________________
# Switch -CleanFolders deletes empty folders older than x days
if ($CleanFolders) {
# Check the version of PowerShell
if ($PSVersionTable.PSVersion.Major -ge "3") {
# PowerShell 3+ Remove empty folders older than (FASTER)
Get-ChildItem -Path $Target -Recurse -Force -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
Where-Object { (Get-ChildItem -Path $_.FullName -Recurse -Force -File) -eq $null } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
"$Timestamp | REMOVED: $Server $Item"
}
} | Tee-Object $LogFile -Append
}
else {
# PowerShell 2 Remove empty folders older than
Get-ChildItem -Path $Target -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
Write-Host "$Timestamp | FAILLED: $Server $Item (IN USE)"
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
Write-Host "$Timestamp | REMOVED: $Server $Item"
"$Timestamp | REMOVED: $Server $Item"
}
} | Out-File $LogFile -Append
}
}
}
# Lact command of the ScriptBlock: Call the magic to happen
Delete-OldFiles $args[0] $args[1] $args[2] -CleanFolders:$args[3]
}
#__________________________________________________________________________________________________________________________________
# Read input file and ignore all lines starting with #
$File = (Import-Csv -Path $ImportFile -Header "A", "B", "C", "D" | Where { $_.A -NotLike "#*" } )
#__________________________________________________________________________________________________________________________________
# If the UNC Path is provided we will run the script locally else it wil be run on the remote server as a job
Foreach ($_ in $File) {
# Define input format & default values
if ($_.A -like "\\*") {
$Server="UNC"
$Target=$_.A
$OlderThanDays=$_.B
$CleanFolders=$_.C
}
else {
$Server=$_.A
$Target=$_.B
$OlderThanDays=$_.C
$CleanFolders=$_.D
}
# Call the scriptblock with the function to run locally or on the remote server
if ($Server -eq "UNC")
{
Write-Host "UNC Path detected: $Target, $OlderThanDays" -ForegroundColor Yellow
Start-Job -ScriptBlock $JobCall -ArgumentList ($Target, $OlderThanDays, $Server) -Name DelFiles
}
else
{
Write-Host "Local path detected: $Server, $Target, $OlderThanDays" -ForegroundColor Cyan
$Credentials = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName,$Password
Invoke-Command -ScriptBlock $JobCall -ArgumentList ($Target, $OlderThanDays, $Server) -ComputerName "$Server.domain" -Authentication Credssp -Credential $Credentials -AsJob -JobName DelFiles
# Delete empty folders: Invoke-Command -ScriptBlock $JobCall -ArgumentList ($Target, $OlderThanDays, $Server, $true) -ComputerName "$Server.domain" -Authentication Credssp -Credential $Credentials -AsJob -JobName DelFiles
}
}
The parameter can't be both Mandatory and have default value. Former is checked first, and if it's set to $true, than default value is simply ignored. If you want to make sure that users can't specify empty value, just use [ValidateNotNullorEmpty()] validation, and make parameter optional.
So I had a similar problem. After months keeping a module with personal functions for day-to-day activities making extensive use of default values and, in many cases, mandatory parameters with initialized default values, suddenly my functions stopped working.
After writing a new one, calling it with explicit parameters would result in the parameters values being empty in the function execution context.
After restarting powershell console for some times I decided to reboot the machine, and everything went back to normal. Go figure.