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.
Related
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"
}
}
I am writing an application that looks through a directory tree and reports if a folder is inactive based on last write time and read only attribute.
However my loop stops after like 7 iterations even though there are thousands of folders.
My code looks like:
function FolderInactive{
Param([string]$Path)
$date = (Get-Date).AddDays(-365)
$anyReadOnly = $false
Get-ChildItem $Path -File -ErrorAction SilentlyContinue | ForEach-Object {
if($_.LastWriteTime -ge $date){
$false
continue
}
if($_.IsReadOnly -eq $false){
$anyReadOnly = $true
}
}
$anyReadOnly
}
Get-ChildItem "some drive" -Recurse | where {$_.PSIsContainer} | Foreach-Object {
Write-Host $_.FullName
FolderInactive($_.FullName)
}
If I comment out the FolderInactive function call in the Foreach loop it prints all the folders, but with the function call it stops after a few iterations. What is happening?
You cannot use continue with the Foreach-Object cmdlet. Foreach-Object is a cmdlet, not a loop. You instead want to use the loop:
function FolderInactive{
Param([string]$Path)
$date = (Get-Date).AddDays(-365)
$anyReadOnly = $false
$items = Get-ChildItem $Path -File -ErrorAction SilentlyContinue
foreach($item in $items)
{
if($item.LastWriteTime -ge $date){
$false
continue
}
if($item.IsReadOnly -eq $false){
$anyReadOnly = $true
}
}
$anyReadOnly
}
This also can be simplified:
function FolderInactive
{
Param([string]$Path)
$date = (Get-Date).AddYears(-1)
$null -ne (Get-ChildItem $Path -File -ErrorAction SilentlyContinue |
Where {$_.LastWriteTime -ge $date -and $_.IsReadOnly})
}
I have written the following script using a post on this forum. The script deletes the files which are older than 15 days:
cls
$servers = Get-Content server.txt
$limit = (Get-Date).AddDays(-15)
$path = "E:\CheckDBOutput"
ForEach ($line in $servers)
{
Invoke-Command -cn $line -ScriptBlock {
Get-ChildItem -Path $path -Recurse -Force | Where-Object {
!$_.PSIsContainer -and $_.CreationTime -lt $limit
} | Remove-Item -Force
}
}
The script is executing fine, but no files are getting deleted.
As already mentioned in the comments, you need to pass the parameters to use inside the scriptblock as arguments to the -ArgumentList parameter:
$RemoveOldFiles = {
param(
[string]$path,
[datetime]$limit
)
Get-ChildItem -Path $path -Recurse -Force | Where-Object {
!$_.PSIsContainer -and $_.CreationTime -lt $limit
} | Remove-Item -Force
}
$servers = Get-Content server.txt
$limit = (Get-Date).AddDays(-15)
$path = "E:\CheckDBOutput"
ForEach ($line in $servers)
{
Invoke-Command -cn $line -ScriptBlock $RemoveOldFiles -ArgumentList $path,$limit
}
Remove-Item does not exist in PowerShell version 1.0 - make sure your destination servers have v2.0 or better installed.
I'm a bit in the dark on what would be best practice. I'm creating a function to delete files older than, but I would like to add a switch called -Remote that is optional, but when you choose to use it, the switch requires mandatory information like $Server to have the whole function below executed on the remote server by using the Invoke-Command.
Something like this:
Delete-OldFiles -Target "\\Share\Dir1" -OlderThanDays "10" -LogName "Auto_Clean.log" -Remote "SERVER1"
The script/function
Function Delete-OldFiles
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,Position=1)]
[ValidateScript({Test-Path $_})]
[String]$Target,
[Parameter(Mandatory=$True,Position=2)]
[Int]$OlderThanDays,
[Parameter(Mandatory=$True,Position=3)]
[String]$LogName
)
if ($PSVersionTable.PSVersion.Major -ge "3") {
# PowerShell 3+ Remove files older than (FASTER)
Get-ChildItem -Path $Target -Exclude $LogName -Recurse -File |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$OlderThanDays) } | ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
# If files can't be removed
if (Test-Path $Item)
{ "$Timestamp | FAILLED: $Item (IN USE)" }
else
{ "$Timestamp | REMOVED: $Item" }
} | Tee-Object $Target\$LogName -Append } # Output file names to console & logfile at the same time
Else {
# PowerShell 2 Remove files older than
Get-ChildItem -Path $Target -Exclude $LogName -Recurse |
Where-Object { !$_.PSIsContainer -and $_.LastWriteTime -lt (Get-Date).AddDays(-$OlderThanDays) } | ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
# If files can't be removed
if (Test-Path $Item)
{
Write-Host "$Timestamp | FAILLED: $Item (IN USE)"
"$Timestamp | FAILLED: $Item (IN USE)"
}
else
{
Write-Host "$Timestamp | REMOVED: $Item"
"$Timestamp | REMOVED: $Item"
}
} | Out-File $Target\$LogName -Append }
}
Delete-OldFiles -Target "\\Share\Dir1" -OlderThanDays "10" -LogName "Auto_Clean.log"
#Delete-OldFiles "E:\Share\Dir1" "5" "Auto_Clean.log"
When I master this I can make the $LogName (logfile) optional to. Thank you for your help. I'm still new to PowerShell and trying to figure this stuff out.
You can use parameters like this
Param (
[switch] $Remote = $false,
[string] $server = $(
if ($Remote)
{
Read-Host -Prompt "Enter remote server:"
}
)
)
In this case, if you call script without -Remote, $server will remain $null.
If you'll call script.ps1 -Remote, it will ask you to enter server name.
If you'll use it like scripts.ps1 -Remote -server "Servername", $server will become Servername.
It can be complicated to wrap function into Invoke-Command based on switch, but you can always use Invoke-Command (it should be as fast as direct command), just use parameters like this
Param (
[switch] $Remote = $false,
[string] $server = $(
if ($Remote)
{
Read-Host -Prompt "Enter remote server:"
}
else
{
"localhost"
}
)
)
I can't seem to find the correct syntax to pass 2 variables from the CALL-script to the execution script in order to have it executed on the remote server. I tried single quotes, double quotes, brackets, .. nothing I can fiind passes the $Target and $OlderThanDays parameters to the script.
Thank you for your help.
The CALL-Script:
#================= VARIABLES ==================================================
$ScriptDir = "\\Server\Scripts"
#================= BODY =======================================================
# Invoke-Command -ComputerName SERVER1 -FilePath $ScriptDir\"Auto_Clean.ps1"
Invoke-Command -FilePath .\Test.ps1 -ComputerName SERVER01 -ArgumentList {-Target ´E:\Share\Dir1\Dir2´,-OlderThanDays ´10´}
The execution Script:
#================= PARAMETERS =================================================
Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$Target,
[Parameter(Mandatory=$True,Position=2)]
[string]$OlderThanDays
)
#================= BODY =======================================================
# Set start time & logname
$StartTime = (Get-Date).ToShortDateString()+", "+(Get-Date).ToLongTimeString()
$LogName = "Auto_Clean.log"
# Format header for log
$TimeStamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()+" |"
$Header = "`n$TimeStamp Deleting files and folders that are older than $OlderThanDays days:`n"
Write-Output "$Header" # to console
Out-File $Target\$LogName -inputobject $Header -Append # to log
# PS 2.0 Workaround (`tee-object -append`) // PS 4.0: `Write-Output "`nDeleting folders that are older than $OlderThanDays days:`n" | Tee-Object $LogFile -Append`
# Remove files older than
Get-ChildItem -Path $Target -Exclude $LogName -Recurse |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$OlderThanDays) } | ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
# If folder can't be removed
if (Test-Path $Item)
{ "$Timestamp | FAILLED: $Item (IN USE)" }
else
{ "$Timestamp | REMOVED: $Item" }
} | Out-File $Target\$LogName -Append
# PS 4.0: ´| Tee-Object $Target\$LogName -Append` # Output folder names to console & logfile at the same time
# Remove empty folders
while (Get-ChildItem $Target -recurse | where {!#(Get-ChildItem -force $_.FullName)} | Test-Path) {
Get-ChildItem $Target -recurse | where {!#(Get-ChildItem -force $_.FullName)} | Remove-Item
}
# Format footer
$EndTime = (Get-Date).ToShortDateString()+", "+(Get-Date).ToLongTimeString()
$TimeTaken = New-TimeSpan -Start $StartTime -End $EndTime
Write-Output ($Footer = #"
Start Time : $StartTime
End Time : $EndTime
Total Runtime : $TimeTaken
$("-"*79)
"#)
# Write footer to log
Out-File -FilePath $Target\$LogName -Append -InputObject $Footer
# Clean up variables
$Target=$StartTime=$EndTime=$OlderThanDays = $null
The execution script:
you have got to use " or ' but not ´ :
-argumentlist #('E:\Share\Dir1\Dir2',10)