Powershell script not recognising Functions - function

I've written the following PS script to delete log files from specific server paths. I'm a novice to PS but I'm getting some errors with a few of the functions that I have written in this script:
#* FileName: FileCleaner.ps1
#Clear the screen
Clear
#Read XML Config File to get settings
[xml]$configfile = Get-Content "C:\Users\pmcma\Documents\Projects\Replace FileCleaner with PowerShell Script\FileCleaner.config.xml"
#Declare and set variables from Config values
$hostServer = $configfile.Settings.HostServer
$dirs = #($configfile.Settings.DirectoryName.Split(",").Trim())
$scanSubDirectories = $configfile.Settings.ScanSubDirectories
$deleteAllFiles = $configfile.Settings.deleteAllFiles
$fileTypesToDelete = #($configfile.Settings.FileTypesToDelete.Split(";").Trim())
$liveSiteLogs = $configfile.Settings.LiveSiteLogs
$fileExclusions = #($configfile.Settings.FileExclusions.Split(";").Trim())
$retentionPeriod = $configfile.Settings.RetentionPeriod
$AICLogs = $configfile.Settings.AICLogs
$AICLogsRententionPeriod = $configfile.Settings.AICLogsRententionPeriod
$fileCleanerLogs = $configfile.Settings.FileCleanerLogs
$fileCleanerLogsRententionPeriod = $configfile.Settings.FileCleanerLogsRententionPeriod
#Setup FileCleaner output success logfiles
$successLogfile = $configfile.Settings.SuccessOutputLogfile
$dirName = [io.path]::GetDirectoryName($successLogfile)
$filename = [io.path]::GetFileNameWithoutExtension($successLogfile)
$ext = [io.path]::GetExtension($successLogfile)
$successLogfile = "$dirName\$filename$(get-date -Format yyyy-MM-dd)$ext"
#Setup FileCleaner output error logfiles
$errorLogfile = $configfile.Settings.ErrorOutputLogfile
$dirName = [io.path]::GetDirectoryName($errorLogfile)
$filename = [io.path]::GetFileNameWithoutExtension($errorLogfile)
$ext = [io.path]::GetExtension($errorLogfile)
$errorLogfile = "$dirName\$filename$(get-date -Format yyyy-MM-dd)$ext"
#Setup Retention Period
$LastWrite = (Get-Date).AddDays(-$retentionPeriod)#.ToString("d")
$AICLastWrite = (Get-Date).AddDays(-$AICLogsRententionPeriod)#.ToString("d")
$fileCleanerLastWrite = (Get-Date).AddDays(-$fileCleanerLogsRententionPeriod)
#EMAIL SETTINGS
$smtpServer = $configfile.Settings.SMTPServer
$emailFrom = $configfile.Settings.EmailFrom
$emailTo = $configfile.Settings.EmailTo
$emailSubject = $configfile.Settings.EmailSubject
#Update the email subject to display the Host Server value
$emailSubject -replace "HostServer", $hostServer
$countUnaccessibleUNCPaths = 0
#Check Logfiles exists, if not create them
if(!(Test-Path -Path $successLogfile))
{
New-Item -Path $successLogfile –itemtype file
}
if(!(Test-Path -Path $errorLogfile))
{
New-Item -Path $errorLogfile –itemtype file
}
foreach ($dir in $dirs)
{
#needs a check to determine if server/the UNC Path is accessible. If it fails to connect, it needs to move on to the next UNC share but a flag needs to
#be generate to alert us to investigate why the UNC share was not accessible during the job run.
If(Test-Path -Path $dir)
{
#write to output logfile Directory info
$Msg = Write-Output "$(Get-Date -UFormat "%D / %T") - Accessing: $dir"
$Msg | out-file $successLogfile
If ($scanSubDirectories -eq "True")
{
If ($deleteAllFiles -eq "True")
{
#ScanSubDirectories and delete all files older than the $retentionPeriod, include Sub-Directories / also forces the deletion of any hidden files
$logFiles = Get-ChildItem -Path $dir -Force -Recurse -Exclude $fileExclusions[0],$fileExclusions[1] | Where { $_.LastWriteTime -le "$LastWrite" }
DeleteLogFiles($logFiles)
#foreach($logFile in $logFiles)
#{
# if($logFile -ne $null)
# {
# $Msg = Write-Output "$("Deleting File $logFile")"
# $Msg | out-file $successLogfile -append
# Remove-Item $logFile.FullName -Force
# }
#}
}
Else
{
#"ScanSubDirectories but only delete specified file types."
$logFiles = Get-Childitem $dir -Include $fileTypesToDelete[0],$fileTypesToDelete[1],$fileTypesToDelete[2], $liveSiteLogs -Recurse -Exclude $fileExclusions[0],$fileExclusions[1] | Where {$_.LastWriteTime -le "$LastWrite"}
DeleteLogFiles($logFiles)
#foreach($logFile in $logFiles)
#{
# if($logFile -ne $null)
# {
# $Msg = Write-Output "$("Deleting File $logFile")"
# $Msg | out-file $successLogfile -append
# Remove-Item $logFile.FullName -Force
# }
#}
}
}
Else
{
#Only delete files in top level Directory
If ($deleteAllFiles -eq "True")
{
$logFiles = Get-ChildItem -Path $dir -Force -Exclude $fileExclusions[0],$fileExclusions[1] | Where { $_.LastWriteTime -le "$LastWrite" }
DeleteLogFiles($logFiles)
#foreach($logFile in $logFiles)
#{
# if($logFile -ne $null)
# {
# $Msg = Write-Output "$("Deleting File $logFile")"
# $Msg | out-file $successLogfile -append
# Remove-Item $logFile.FullName -Force
# }
#}
}
Else
{
$logFiles = Get-Childitem $dir -Include $fileTypesToDelete[0],$fileTypesToDelete[1],$fileTypesToDelete[2], $liveSiteLogs -Exclude $fileExclusions[0],$fileExclusions[1] | Where {$_.LastWriteTime -le "$LastWrite"}
DeleteLogFiles($logFiles)
#foreach($logFile in $logFiles)
#{
# if($logFile -ne $null)
# {
# $Msg = Write-Output "$("Deleting File $logFile")"
# $Msg | out-file $successLogfile -append
# Remove-Item $logFile.FullName -Force
# }
#}
}
}
}
Else
{
$countUnaccessibleUNCPaths++
#server/the UNC Path is unaccessible
$Msg = Write-Output "$(Get-Date -UFormat "%D / %T") Unable to access $dir."
$Msg | out-file $errorLogfile -append
}
# Call the function to Delete the AIC XML Logfiles
DeleteAICXMLLogs $dir
}
#If any of the directories were unaccessible send an email to alert the team
if($countUnaccessibleUNCPaths.count -gt 0)
{
# Call the function to send the email
SendEmail $emailSubject $emailFrom $emailTo
}
#Only keep 2 weeks worth of the FileCleaner App logs for reference purposes
If(Test-Path -Path $fileCleanerLogs)
{
#write to output logfile Directory info
$Msg = Write-Output "$(Get-Date -UFormat "%D / %T") - Accessing: $fileCleanerLogs"
$Msg | out-file $successLogfile
$fileCleanerLogs = Get-Childitem $fileCleanerLogs -Recurse | Where {$_.LastWriteTime -le "$fileCleanerLastWrite"}
DeleteLogFiles($fileCleanerLogs)
#foreach($fileCleanerLog in $fileCleanerLogs)
#{
# if($fileCleanerLog -ne $null)
# {
# $Msg = Write-Output "$("Deleting File $fileCleanerLog")"
# $Msg | out-file $successLogfile -append
# Remove-Item $fileCleanerLog.FullName -Force
# }
#}
}
Function DeleteLogFiles($logFiles)
{
foreach($logFile in $logFiles)
{
if($logFile -ne $null)
{
$Msg = Write-Output "$("Deleting File $logFile")"
$Msg | out-file $successLogfile -append
Remove-Item $logFile.FullName -Force
}
}
}
Function DeleteAICXMLLogs($dir)
{
#Split the UNC path $dir to retrieve the server value
$parentpath = "\\" + [string]::join("\",$dir.Split("\")[2])
#test access to the \\server\D$\DebugXML path
If(Test-Path -Path $parentpath$AICLogs)
{
$Msg = Write-Output "$(Get-Date -UFormat "%D / %T") - Accessing: $parentpath$AICLogs"
$Msg | out-file $successLogfile
#Concantenate server value to $AICLogs to delete all xml logs in \\server\D$\DebugXML with a retention period of 30Days
$XMLlogFiles = Get-ChildItem -Path $parentpath$AICLogs -Force -Include $fileTypesToDelete[3]-Recurse -Exclude $fileExclusions[0],$fileExclusions[1] | Where { $_.LastWriteTime -le "$AICLastWrite" }
#get each file and add the filename to be deleted to the successLogfile before deleting the file
DeleteLogFiles($XMLlogFiles)
#foreach($XMLlogFile in $XMLlogFiles)
#{
# if($XMLlogFile -ne $null)
# {
# $Msg = Write-Output "$("Deleting File $XMLlogFile")"
# $Msg | out-file $successLogfile -append
# Remove-Item $XMLlogFile.FullName -Force
# }
#}
}
Else
{
$Msg = Write-Output "$("$parentpath$AICLogs does not exist.")"
$Msg | out-file $successLogfile -append
}
}
Function SendEmail($emailSubject, $emailFrom, $emailTo)
{
$MailMessage = New-Object System.Net.Mail.MailMessage
$SMTPClient = New-Object System.Net.Mail.smtpClient
$SMTPClient.host = $smtpServer
$Recipient = New-Object System.Net.Mail.MailAddress($emailTo, "Recipient")
$Sender = New-Object System.Net.Mail.MailAddress($emailFrom, "Sender")
$MailMessage.Sender = $Sender
$MailMessage.From = $Sender
$MailMessage.Subject = $emailSubject
$MailMessage.Body = #"
This email was generated because the FileCleaner script was unable to access some UNC Paths, please refer to $errorLogfile for more information.
Please inform the Team if you plan to resolve this.
This is an automated email please do not respond.
"#
$SMTPClient.Send($MailMessage)
}
when debugging I'm getting these errors:
DeleteAICXMLLogs : The term 'DeleteAICXMLLogs' is not recognized as
the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify
that the path is correct and try again. At
C:\Users\pmcma\Documents\Projects\Replace FileCleaner with PowerShell
Script\FileCleaner.ps1:158 char:5
+ DeleteAICXMLLogs $dir
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (DeleteAICXMLLogs:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
SendEmail : The term 'SendEmail' is not recognized as the name of a
cmdlet, function, script file, or operable program. Check the spelling
of the name, or if a path was included, verify that the path is
correct and try again. At C:\Users\pmcma\Documents\Projects\Replace
FileCleaner with PowerShell Script\FileCleaner.ps1:164 char:5
+ SendEmail $emailSubject $emailFrom $emailTo
+ ~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (SendEmail:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
DeleteLogFiles : The term 'DeleteLogFiles' is not recognized as the
name of a cmdlet, function, script file, or operable program. Check
the spelling of the name, or if a path was included, verify that the
path is correct and try again. At
C:\Users\pmcma\Documents\Projects\Replace FileCleaner with PowerShell
Script\FileCleaner.ps1:175 char:5
+ DeleteLogFiles($fileCleanerLogs)
+ ~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (DeleteLogFiles:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
I don't see anything wrong with how I'm declaring the functions or calling them. Any ideas why this script is failing?

PowerShell Scripts are read from the top to the bottom, so you can't use any references before they are defined, most probably that is why you are receiving errors.
Try adding your function definition blocks above the point where you call them.
Alternatively you can make a function having global scope. Just preface the function name with the keyword global: like,
function global:test ($x, $y)
{
$x * $y
}

I've had this happen as well. Try placing the functions before the business logic. This is a script, not compiled code. So the functions are yet to be declared before you are calling them.

Related

How to call a function within a foreach parallel loop

Good evening.
I'm trying to use parallelism for the first time but I don't understand how to call a function within foreach loop.
I get a series of this error: Cannot bind argument to parameter 'Path' because it is null.
This is what I've done so far:
$FolderPath = "C:\myfolder\"
function AppendLog ($client) {
$so = New-CimSessionOption -Protocol 'DCOM'
$s = New-CimSession -ComputerName $client -SessionOption $so
Add-Content -Path (join-path $folderpath "LOGS.txt") -Value ( (get-date -Format "[yyyy.MM.dd HH:mm:ss]").tostring() + $client + " -PING: OK")
$arch = Get-CimInstance –query "select * from win32_operatingsystem" -CimSession $s | select -expandproperty osarchitecture
Add-Content -Path (join-path $folderpath "LOGS.txt") -Value ( (get-date -Format "[yyyy.MM.dd HH:mm:ss]").tostring() + $client + " -ARCH:" + $arch )
$lastboot = Get-CimInstance –query "select * from win32_operatingsystem" -CimSession $s | select -expandproperty lastbootuptime
Add-Content -Path (join-path $folderpath "LOGS.txt") -Value ( (get-date -Format "[yyyy.MM.dd HH:mm:ss]").tostring() + $client + " -BOOT:" + $lastboot )
}
$funcDef = $function:AppendLog.ToString()
$clients = get-content -path (join-path $folderPath "client.txt")
$clients | ForEach-Object -parallel {
if (test-connection $_ -count 2 -Quiet)
{
$function:AppendLog = $using:funcDef
AppendLog ($_)
}
} -throttlelimit 3
Could you explain me how to pass my path?
My bad on the comment, the error you're getting is most likely coming from your function. The error is being thrown by Join-Path:
PS /> Join-Path $null 'Logs.txt'
Join-Path : Cannot bind argument to parameter 'Path' because it is null.
At line:1 char:11
+ Join-Path $null 'Logs.txt'
+ ~~~~~
+ CategoryInfo : InvalidData: (:) [Join-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.JoinPathCommand
The reason is because $FolderPath doesn't exist in the scope of your parallel loop. $folderpath should be replace with $using:folderpath inside your function.
As a side note, adding information to the same file on a parallel execution doesn't seem to be a good idea.
Last edit, I understand if this is meant to test how ForEach-Object -Parallel works but again, if the cmdlet allows remote querying / remote execution with multiple hosts at the same time, let the cmdlet handle that for you, it is more efficient.
As for the code, this is what I would use with what you already have:
$FolderPath = "C:\myfolder\"
$sessionOption = New-CimSessionOption -Protocol 'DCOM'
$clients = Get-Content -Path (Join-Path $FolderPath -ChildPath "Client.txt")
$results = $clients | ForEach-Object -Parallel {
$out = #{
Time = [datetime]::Now.ToString('[yyyy.MM.dd HH:mm:ss]')
ComputerName = $_
}
if ($ping = Test-Connection $_ -Count 2 -Quiet)
{
$session = New-CimSession -ComputerName $_ -SessionOption $using:sessionOption
$OSInfo = Get-CimInstance -CimSession $session -ClassName win32_operatingSystem
Remove-CimSession $session
}
$out.Ping = $ping
$out.Arch = $OSInfo.OSArchitecture
$out.LastBoot = $OSInfo.LastBootUpTime
[pscustomobject]$out
} -ThrottleLimit 3
$results | Export-Csv "$myFolder\LOGS.csv" -NoTypeInformation
This will output an object like this below:
Time ComputerName Ping OSArchitecture LastBoot
---- ------------ ---- -------------- --------
[2021.06.19 20:06:00] ComputerName01 True 64-bit 6/16/2021 11:47:16 AM
[2021.06.19 20:07:00] ComputerName02 False
[2021.06.19 20:08:00] ComputerName03 True 64-bit 6/13/2021 11:47:16 AM
[2021.06.19 20:09:00] ComputerName04 True 64-bit 6/14/2021 11:47:16 AM
[2021.06.19 20:10:00] ComputerName05 True 64-bit 6/15/2021 11:47:16 AM
Which can be exported nicely to a CSV instead of a text file. P.D.: sorry for the syntax highlighting :(

Function to check file in 2 file locations not working

I am trying to create a program that will copy files from our backup to archive directory using Powershell. I have two criteria in order for this program to run smoothly. One is that we have files from both current year and past years so only the files from this year must be copied over. Another is that we have to check to make sure that we are not copying over files of the same file name in case if the data in the file is accidentally modified. Whenever I have this program not in a function, it works. But in a function, it gives me errors that it "cannot find the path" of the folder that I am copying from and the folder that I'm pasting the files to. I am going to use this for more than sixty locations, so it would be better that I don't have to rewrite the code in the function sixty times. I thought about using Robocopy, but I am still getting the same issues regardless with files not being copied over.
Function Copy-Data {
param (
[system.object]$copyFolder,
[system.object]$pasteFolder,
[int]$currentYear,
[int]$lastYear,
[int]$nextYear)
$copyItem = Get-ChildItem -Path $copyFolder
$pasteItem = Get-ChildItem -Path $pasteFolder
$copyCount = $copyItem.count
for ($i = 0; $i -lt $copyCount; $i++)
{
$copyName = $copyItem.Name
$testPath = Test-Path "$pasteFolder$copyName"
if ($copyItem[$i].LastWriteTime -gt $firstDate -and $copyItem[$i].LastWriteTime -lt $lastDate)
{
if ($testPath -eq $false)
{
Copy-Item -Path $copyFolder$copyName -Destination $pasteFolder
#Robocopy "$copyFolder$copyItem[$i]" "$pasteFolder"
Write-Host $pasteFolder$copyName
}
}
}
}
$currentYear = Get-Date -Format "yyyy"
$lastYear = [int]$currentYear - 1
$nextYear = [int]$currentYear + 1
$firstDate = "12/31/$lastYear"
$lastDate = "01/01/$nextYear"
$copyFolder = "\\fileshare\test\copy\"
$pasteFolder = "\\fileshare\test\$currentYear\paste\"
Copy-Data ($copyFolder, $pasteFolder, $currentYear, $lastYear, $nextYear)
I feel like you are making this more complicated than it needs to be.
Core code can be something like:
Get-ChildItem -Path $copyFolder |
ForEach-Object{
If( !(Test-Path $pasteFolder -Name $_.Name ) )
{
Copy-Item $_.FullName -Destination $pasteFolder
}
}
You can use just the destination path you don't have to give the full path of the destination file.
As a function it may look something like:
Function CopyFoldercontents
{
Param(
[Parameter( Mandatory = $true, Position = 0)]
[String]$copyFolder,
[Parameter( Mandatory = $true, Position = 1)]
[String]$pasteFolder
) # End Parameter Block.
Get-ChildItem -Path $copyFolder |
ForEach-Object{
If( !(Test-Path $pasteFolder -Name $_.Name ) )
{
Copy-Item $_.FullName -Destination $pasteFolder
}
}
} # End Function CopyFolderContents
This could be more robust though, depends on what direction you want to take it.
Continuing from my comment.
You could just do this...
(validate what you are after on both sides, the construct the final function)
Function Start-FolderMirror
{
[CmdletBinding()]
[Alias('mir')]
Param
(
[string]$SourcePath = (Read-Host -Prompt 'Enter a source path'),
[string]$DestinationPath = (Read-Host -Prompt 'Enter a destination path')
)
$SourceFiles = (Get-ChildItem -Path $SourcePath -File).FullName
$DestinationFiles = (Get-ChildItem -Path $DestinationPath -File).FullName
Compare-Object -ReferenceObject $SourceFiles -DifferenceObject $DestinationFiles -IncludeEqual |
Select-Object -First 5
}
Start-FolderMirror -SourcePath 'd:\temp' -DestinationPath 'D:\temp\TestFiles'
# Results
<#
InputObject SideIndicator
----------- -------------
D:\temp\TestFiles\abc - Copy - Copy.bat =>
D:\temp\TestFiles\abc - Copy.bat =>
D:\temp\TestFiles\abc.bat =>
D:\temp\(MSINFO32) command-line tool switches.pdf <=
D:\temp\23694d1213305764-revision-number-in-excel-book1.xls <=
#>
Function Start-FolderMirror
{
[CmdletBinding(SupportsShouldProcess)]
[Alias('mir')]
Param
(
[string]$SourcePath = (Read-Host -Prompt 'Enter a source path'),
[string]$DestinationPath = (Read-Host -Prompt 'Enter a destination path')
)
$SourceFiles = (Get-ChildItem -Path $SourcePath -File).FullName
$DestinationFiles = (Get-ChildItem -Path $DestinationPath -File).FullName
Compare-Object -ReferenceObject $SourceFiles -DifferenceObject $DestinationFiles -IncludeEqual |
Select-Object -First 5 |
Where-Object -Property SideIndicator -Match '<='
}
Start-FolderMirror -SourcePath 'd:\temp' -DestinationPath 'D:\temp\TestFiles' -WhatIf
# Results
<#
InputObject SideIndicator
----------- -------------
D:\temp\(MSINFO32) command-line tool switches.pdf <=
D:\temp\23694d1213305764-revision-number-in-excel-book1.xls <=
#>
Function Start-FolderMirror
{
[CmdletBinding(SupportsShouldProcess)]
[Alias('mir')]
Param
(
[string]$SourcePath = (Read-Host -Prompt 'Enter a source path'),
[string]$DestinationPath = (Read-Host -Prompt 'Enter a destination path')
)
$SourceFiles = (Get-ChildItem -Path $SourcePath -File).FullName
$DestinationFiles = (Get-ChildItem -Path $DestinationPath -File).FullName
Compare-Object -ReferenceObject $SourceFiles -DifferenceObject $DestinationFiles -IncludeEqual |
Select-Object -First 5 |
Where-Object -Property SideIndicator -Match '<=' |
ForEach {Copy-Item -Path $PSItem.InputObject -Destination $DestinationPath}
}
Start-FolderMirror -SourcePath 'd:\temp' -DestinationPath 'D:\temp\TestFiles' -WhatIf
# Results
<#
What if: Performing the operation "Copy File" on target "Item: D:\temp\(MSINFO32) command-line tool switches.pdf Destination: D:\temp\TestFiles\(MSINFO32) command-line tool switches.pdf".
What if: Performing the operation "Copy File" on target "Item: D:\temp\23694d1213305764-revision-number-in-excel-book1.xls Destination: D:\temp\TestFiles\23694d1213305764-revision-number-in-excel-book1.xls".
#>

Returning data calling function from invoke-command with parameters

Apologize for the length..
Trying to modify a script that currently uses robocopy to report a folder path's size, child folder count, and size. This script currently takes about 24 hrs to run as it goes one by one to each folder. I'm trying to implement invoke-command to set-up jobs so that it will run at least 10 instances of robocopy in the hopes if drastically reducing run time.
I've tried multiple variances given multiple resources to set parameters, call a function and get the results back with no success.
with current code (below) getting error;
Invoke-Command : Parameter set cannot be resolved using the specified named parameters.
ERROR: + Invoke-Command -ScriptBlock { param ($item,$Filter,$params) $ ...
ERROR: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ERROR: + CategoryInfo : InvalidArgument: (:) [Invoke-Command], ParameterBindingException
ERROR: + FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.PowerShell.Commands.InvokeCommandCommand
Function ListFolder ($Folder, $FilterSet, $Roboparam)
{
write-host $Folder
Try
{
$Folder = (Resolve-Path -LiteralPath $Folder -ErrorAction Stop).ProviderPath
If (-Not (Test-Path -LiteralPath $Folder -Type Container -ErrorAction Stop))
{Write-Warning ("{0} is not a directory and will be skipped" -f $Folder)
Return
}
$Script = robocopy $Folder NULL $FilterSet $FRoboparam
$exit_code = $LASTEXITCODE
16, 8, 4, 2, 1 | % {
Switch ($exit_code -band $_)
{
16 { $exit_reason = "Usage error or insufficient access privileges" }
8 { $exit_reason = "Retry limit exceeded" }
4 { $exit_reason = "Some Mismatched files or directories were detected" }
2 { $exit_reason = "Some Extra files or directories were detected. No files were copied" }
1 { $exit_reason = " " }
}
}
If ($exit_code -eq 0)
{$exit_reason = 'No Change'}
If ($Script[-6] -match $dirPattern)
{$Dir = $matches.Dir}
Else
{$Dir = 0}
If ($Script[-5] -match $countPattern)
{$Count = $matches.Count}
Else
{$Count = 0}
If ($Count -gt 0)
{If ($Script[-4] -match $sizePattern)
{$FSize = $matches.Size}}
Else
{$FSize = 0}
$Script:TotSize += $FSize
$Script:Report += New-Object PSObject -Property #{
'Folder Name' = $Folder
Files = "{0:N0}" -f [int]$Count
Folders = "{0:N0}" -f [int]$Dir
Size = "{0:N0}" -f [long]$FSize
Comment = $exit_reason}
Clear-Variable -Name Dir
Clear-Variable -Name Count
Clear-Variable -Name FSize
If ($exit_reason) { Clear-Variable -Name exit_reason }}
Catch
{$Script:Report += New-Object PSObject -Property #{
'Folder Name' = $Folder
Files = "{0:N0}" -f [int]$Count
Folders = "{0:N0}" -f [int]$Dir
Size = [long]$FSize
Comment = [string]$_.Exception.Message}
Continue
}
Return #$Script
}
}
$params = New-Object System.Collections.Arraylist
$params.AddRange(#("/L","/S","/NJH","/BYTES","/FP","/NFL","/NC","/NDL","/TS","/XJ","/R:0","/W:0"))
$dirPattern = "^\s{4}Dirs\s:\s+(?<Dir>\d+).*"
$countPattern = "^\s{3}Files\s:\s+(?<Count>\d+).*"
$sizePattern = "^\s{3}Bytes\s:\s+(?<Size>\d+(?:\.?\d+)).*"
If ($PSBoundParameters['Force']) {$FileDate = 10}
If ($FileDate -lt 6){Exit}
Else
{$Paths = Get-Content $InputFile
ForEach ($item in $Paths)
{
$MaxThreads = 10
While (#(Get-Job | where { $_.State -eq "Running" }).Count -ge $MaxThreads)
{
Write-Host "Waiting for open thread...($MaxThreads Maximum)"
Start-Sleep -Seconds 3
}
Invoke-Command -Computer . -ScriptBlock { param ($item,$Filter,$params) ${function:ListFolder} } -ArgumentList $item, $Filter, $params -AsJob
}
}
When all said and done I have a collection of $Report to show folder name, count of files, count of folders, size in bytes, and comments tho show any errors to Excel
$Report | Sort-Object {[long]$_.Size} -descending | Select 'Folder Name', Files, Folders, Size, Comment | Export-Csv -Path $ReportPath\$(Get-Date -uformat "%Y_%m_%d")-FileSizes.csv -Encoding ascii -NoTypeInformation

Can't export csv in powershell, empty CSV file

I'm trying to get what permissions files and folders have and export to a csv file. I can get the info to display on screen, but when I try to export it the resulting csv file is empty.
The code:
function Test-IsWritable(){
<#
.Synopsis
Command tests if a file is present and writable.
.Description
Command to test if a file is writeable. Returns true if file can be opened for write access.
.Example
Test-IsWritable -path $foo
Test if file $foo is accesible for write access.
.Example
$bar | Test-IsWriteable
Test if each file object in $bar is accesible for write access.
.Parameter Path
Psobject containing the path or object of the file to test for write access.
#>
[CmdletBinding()]
param([Parameter(Mandatory=$true,ValueFromPipeline=$true)][psobject]$path)
process{
Write-Host "Test if file $path is writeable"
if (Test-Path -Path $path -PathType Any){
$target = Get-Item $path -Force
try{
$writestream = $target.Openwrite()
$writestream.Close() | Out-Null
Remove-Variable -Name writestream
Write-Host "File is writable" -ForegroundColor DarkGreen
Write-Output $true
}
catch{
Write-Host "File is not writable" -ForegroundColor DarkRed
Write-Output $false
}
Remove-Variable -Name target
}
else{
Write-Host "File $path does not exist or is a directory" -ForegroundColor Red
Write-Output $false
}
}
}
write-host "WARNING: If checking deep folders (where the full path is longer than 248 characters) please " -foregroundcolor Yellow -NoNewline
Write-Host "MAP THE DRIVE " -ForegroundColor Red -NoNewline
Write-Host "in order to keep the names as short as possible" -ForegroundColor Yellow
$basefolder = Read-Host -Prompt 'What is the folder or files you want to get permissions of?'
write-host "WARNING: if permissions.csv already exists, it will be overwritten!" -foregroundcolor Yellow
Write-Host 'Export results to CSV? (y/n): ' -ForegroundColor Magenta -NoNewline
$export = Read-Host
if ($export -like "y")
{
Write-Host "Name the file (ex: permissions.csv): " -ForegroundColor Magenta -NoNewline
$FileName = Read-Host
$Outfile = “$PSScriptRoot\$FileName”
write-host "Will write results to $PSScriptRoot\$FileName" -ForegroundColor Green
}
else
{
write-host "User did not type 'y', continuing" -ForegroundColor DarkYellow
}
$files = get-childitem $basefolder -recurse -File
Write-Host $files
Write-Host "=========================" -ForegroundColor Black
#$subfiles = Get-ChildItem $folders -Recurse -File
#Write-Host $folders
#Write-Host "=========================" -ForegroundColor Black
#Write-Host $subfiles
$results = foreach($folder in $files) {
New-Object psobject -Property #{
File = $folder;
Access = "$basefolder\$folder" | Test-IsWritable
}
Write-Host $folder
}
#$subresults = foreach($subfile in $subfiles) {
# New-Object psobject -Property #{
# File = $subfiles;
# Access = $subfile | Test-IsWritable;
# }
#}
Write-Host $results
Write-Host "Finished combo loop, exporting..." -ForegroundColor Green
$results | Export-Csv $Outfile -NoTypeInformation -Delimiter ";"
Write-Host "Converting delimited CSV to Column Excel Spreadsheet"
$outputXLSX = $PSScriptRoot + "\$Filename.xlsx"
$excel = New-Object -ComObject excel.application
$workbook = $excel.Workbooks.Add(1)
$worksheet = $workbook.worksheets.Item(1)
$TxtConnector = ("TEXT;" + $Outfile)
$Connector = $worksheet.QueryTables.add($TxtConnector,$worksheet.Range("A1"))
$query = $worksheet.QueryTables.item($Connector.name)
$query.TextFileOtherDelimiter = ';'
$query.TextFileParseType = 1
$query.TextFileColumnDataTypes = ,2 * $worksheet.Cells.Columns.Count
$query.AdjustColumnWidth = 1
$query.Refresh()
$query.Delete()
$Workbook.SaveAs($outputXLSX,51)
$excel.Quit()
Remove-Item $Outfile
Write-Host "See $PSScriptRoot\$Filename.xlsx for results" -ForegroundColor Green
UPDATE: Mostly working, strange output though:
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
The specified path, file name, or both are too
long. The fully qualified file name must be less than 260 characters,
and the directory name must be less than 248 characters.
In the next column:
FileAccess
FullControl
FullControl
FullControl
Modify, Synchronize
ReadAndExecute, Synchronize
Modify, Synchronize
Modify, Synchronize
FullControl
FullControl
FullControl
Modify, Synchronize
...
The specified path, file name, or both are too long. The fully
qualified file name must be less than 260 characters, and the
directory name must be less than 248 characters.
I'm not sure why it's showing multiple rows for the same file, I'd like to have 1 row per file with the true File Access.
Remove Write-Host before using Export-Csv. Write-Hostconsumes the data from the pipeline and only outputs it on screen.
#(...)
$i = 0
$results = foreach($acl in $acls) {
$folder = (Convert-Path $acl.pspath)
Write-Progress -Activity "Getting Security" -Status "checking $folder" -PercentComplete ($i / $folders.Count * 100)
foreach($access in $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])) {
New-Object psobject -Property #{
Folder = $folder;
User = $acl.Owner;
Group=$acl.Group;
Mode = $access.AccessControlType;
FileAcess = $access.FileSystemRights;
}
}
$i++
}
Write-Host "Reached End, exporting..." -ForegroundColor Green
$results | Export-Csv $Outfile -NoTypeInformation -Delimiter ";"

Passing properties to a function pipeline output error

I'm getting this error when I add to the return the -prefix "File_Name" to the end of Return $Results | Out-Excel -Prefix "File_Name" if I leave -prefix "File_Name" off of the script it works. What am I missing?
Out-Excel : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of
the parameters that take pipeline input.
At \\util01\cs\Scripts\PowerShell\BradleyDev\Google Chrome\Set-Chrome-Service-ON-V3.ps1:52 char:21
+ } Return $Results | Out-Excel -Prefix "File_Name"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (#{PC=CD-100TRAINER; Status=OFF-LINE}:PSObject) [Out-Excel], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Out-Excel
foreach ($PC in $Process){
$counter ++
$Service = "gupdate"
$State = "delayed-auto"
$command = "sc.exe \\$PC config $Service start= $State"
$count = $Process.Count
$command2 = "sc.exe \\$PC config gupdatem start= demand"
if (test-connection -computername $PC -BufferSize 16 -Count 1 -quiet) {
$Output = Invoke-Expression -Command $Command -ErrorAction Stop
$Output = Invoke-Expression -Command $Command2 -ErrorAction Stop
if($LASTEXITCODE -ne 0){
Write-Host "$counter of $count : $PC : Failed More details: $Output" -foregroundcolor White -BackgroundColor RED
$status = [string]$Output
} else {
Write-Host "$counter of $count : $PC : Success" -foregroundcolor DarkGreen
$status = "Success"
}
} else {
Write-Host "$counter of $count : $PC : OFF-LINE" -foregroundcolor black -BackgroundColor gray
$status = "OFF-LINE"
}
$PCobject = [PSCustomObject] #{
PC = $PC
Status = $status
}
$Results +=$PCobject
} Return $Results | Out-Excel -Prefix "File_Name"
function Out-Excel {
param(
[Parameter(Mandatory=$True,HelpMessage="Enter Prefix",ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[ValidateNotNullorEmpty()]
[string[]]$Prefix = $null,
$Path = "$env:temp\$Prefix`_$(Get-Date -Format yyMMdd_HHmmss).csv"
)
$input | Export-CSV -Path $Path -UseCulture -Encoding UTF8 -NoTypeInformation
Invoke-Item -Path $Path
} #end function
Updated but still failing:
function Out-Excel {
param(
[Parameter(Mandatory=$True,HelpMessage="Enter Prefix")]
[ValidateNotNullorEmpty()]
[string[]]$Prefix = $null,
$Path = "$env:temp\$Prefix`_$(Get-Date -Format yyMMdd_HHmmss).csv"
)
$input | Export-CSV -Path $Path -UseCulture -Encoding UTF8 -NoTypeInformation
Invoke-Item -Path $Path
} #end function
ERROR:
Out-Excel : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and
its properties do not match any of the parameters that take pipeline input.
At \\util01\cs\Scripts\PowerShell\BradleyDev\Google Chrome\Set-Chrome-Service-ON-V3.ps1:52 char:14
+ } $Results | Out-Excel -Prefix "Services_Results"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (#{PC=CD-100TRAINER; Status=OFF-LINE}:PSObject) [Out-Excel], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Out-Excel
This works:
I wanted to be able to use the function and set a file name prefix depending on name of the results. The code below works by adding another parameter "the object" I want to pass to the function, however I loose the ability to pipe the object to the function. Apparently if I use $input I can not take pipeline arguments?
function Out-Excel {
param(
[Parameter(Mandatory=$True,HelpMessage="Enter Prefix")]
[ValidateNotNullorEmpty()]
[string[]]$Prefix = $null,
[Parameter(Mandatory=$True,HelpMessage="Enter Prefix")]
[ValidateNotNullorEmpty()]
[object]$Object = $null,
$Path = "$env:temp\$Prefix`_$(Get-Date -Format yyMMdd_HHmmss).csv"
)
$Object | Export-CSV -Path $Path -UseCulture -Encoding UTF8 -NoTypeInformation
Invoke-Item -Path $Path
} #end function
$Results +=$PCobject
} Out-Excel -object $Results -Prefix "Services_Results"`
If you want to accept pipeline input from advanced function, then it have to have parameters, which declared as ValueFromPipeline or ValueFromPipelineByPropertyName and does not bound from command line.
function Out-Excel {
param(
[Parameter(Mandatory=$True,HelpMessage="Enter Prefix")]
[string]$Prefix = $null,
[Parameter(Mandatory=$True,ValueFromPipeline=$True)]
[psobject]$InputObject = $null,
$Path = "$env:temp\$Prefix`_$(Get-Date -Format yyMMdd_HHmmss).csv"
)
begin{
$List=New-Object System.Collections.Generic.List[PSObject]
}
process{
$List.Add($InputObject)
}
end{
$List | Export-CSV -Path $Path -UseCulture -Encoding UTF8 -NoTypeInformation
Invoke-Item -Path $Path
}
} #end function