Passing a path directory to a function in PowerShell - powershell

I am new to PS and I am trying to write a function which takes in parameters from a global variable. I want to pass a path name read from a .txt file into a function in the same script.
function GetCorrectChildren ([string] $homepath,$min,$max,$row)
{
#Testpoint 2
write-host "homepath = $homepath"
$ColItem = (Get-ChildItem $homepath |? {$_.PSIsContainer} | sort-object)
}
foreach ($homepath in (Get-Content $PSScriptRoot\homepath_short.txt))
{
$freeSpace = [win32api]::GetDiskFreeSpace("$homepath").FreeBytesAvailable / 1073741824
$totalSpace = [win32api]::GetDiskFreeSpace("$homepath").TotalNumberOfBytes / 1073741824
$percentageFreeSpace = $freeSpace / $totalSpace * 100
if($freeSpace -lt $threshold)
{
#Testpoint 1
write-host "homepath = $homepath"
GetCorrectChildren ("$homepath",$min,$max,$OriRow)
}
For #Testpoint 1, it returns the path name correctly which is \\C:\test1\test_a. However in #Testpoint 2 it returns \\C:\test1\test_a 20 30 System.Object.
I don't understand what does the 20 30 System.Object mean and where does it come from? Can some one shine some light on this? Thanks

Change the last line
GetCorrectChildren ("$homepath",$min,$max,$OriRow)
to
GetCorrectChildren $homepath $min $max $OriRow
as ("$homepath",$min,$max,$OriRow) creates a single array with the four values and passes it to the function GetCorrectChildren as its first parameter so that write-host "homepath = $homepath" in it will print all 4 values

Related

How can I get a count of directories and files in a base directory and each descendant directory?

I am counting files and folders inside a path, which has several files as well as folders.
I am using this:
dir -recurse | ?{ $_.PSIsContainer } | %{ Write-Host $_.FullName (dir $_.FullName | Measure-Object).Count }
Problem is that I want to know if the count contains sub folders. Also, the above command is not giving me the file count of the base folder.
e.g.
powershell\
Copy_files.ps1
powershell.txt
sortbydate.ps1
linux\
New Text Document (2).txt
New Text Document.txt
For above file structure, I am getting :
linux 2
While I wanted:
Powershell 3 ( + 1 directory )
linux 2
This is my second attempt - Kind of ugly, but appears to do the job.
Get-ChildItem -Recurse -Directory | ForEach-Object {
[Object[]]$Dirs = (Get-ChildItem -Path $_.FullName -Directory)
$DirText = if($Dirs.Length -gt 0) {" ( + $($Dirs.Length) directory )"} else {''}
[Object[]]$Files = (Get-ChildItem -Path $_.FullName -File)
"$(' ' * ($_.FullName -split '\\').Length)$($_.Name) $($Files.Length)$DirText"
}
dir is an alias for Get-ChildItem which, as the name implies, gets the children of the specified location but not an item representing that location itself; for that you'll need Get-Item. It would appear that you're executing that pipeline in the powershell directory and, once ?{ $_.PSIsContainer } filters out any child files, %{ ... } is executed on the sole child directory, linux.
Basic implementations
What you can do is...
Starting from some base location (the current directory)...
Enumerate the children of the current location, keeping track of the count of containers (directories) and leaves (files) along the way
When a container is encountered, either start processing it immediately or store it for processing later
When all children have been enumerated, output the results for the current container
With this approach each container and leaf is "touched" (enumerated) exactly once.
Iterative implementation
This implements the above steps using a [Queue[]] of containers to process; a [Stack[]] could also be used. Since traversal begins with one container to process (the current location), a do { } while () loop is used to continue executing if there are any more containers to traverse.
# Stores the current container being processed; initialize to the current location
$current = Get-Item -Path '.' -Force
# Stores containers that have been encountered but not yet processed
# Using a Queue[PSObject] with Enqueue()/Dequeue() results in breadth-first traversal
# Using a Stack[PSObject] with Push()/Pop() results in depth-first traversal
$pendingContainers = New-Object -TypeName 'System.Collections.Generic.Queue[PSObject]'
do
{
$containerCount = 0
$leafCount = 0
foreach ($child in Get-ChildItem -LiteralPath $current.PSPath -Force)
{
if ($child.PSIsContainer)
{
$containerCount++
# Store the child container for later processing
$pendingContainers.Enqueue($child)
}
else
{
$leafCount++
}
}
# Write-Output is superfluous here, though makes it explicit that its input will be sent down the pipeline
Write-Output -InputObject (
[PSCustomObject] #{
Name = $current.PSChildName
ContainerCount = $containerCount
LeafCount = $leafCount
TotalCount = $containerCount + $leafCount
}
)
}
# Assign the next container to $current, if available; otherwise, exit the loop
# The second operand to -and works because assigning a non-$null value evaluates to $true
while ($pendingContainers.Count -gt 0 -and ($current = $pendingContainers.Dequeue()))
# For PowerShell (Core) 6+: while ($pendingContainers.TryDequeue([Ref] $current))
Using Group-Object for counting
You can use the Group-Object cmdlet to build a [Hashtable] keyed on whether a child is a container ($true) or a leaf ($false). This simplifies the above code a little at the expense of decrease efficiency and increased memory usage. Note that this can be adapted similarly for use in the recursive implementation in the next section.
# Stores the current container being processed; initialize to the current location
$current = Get-Item -Path '.' -Force
# Stores containers that have been encountered but not yet processed
# Using a Queue[PSObject] with Enqueue()/Dequeue() results in breadth-first traversal
# Using a Stack[PSObject] with Push()/Pop() results in depth-first traversal
$pendingContainers = New-Object -TypeName 'System.Collections.Generic.Queue[PSObject]'
do
{
$childrenByIsContainer = Get-ChildItem -LiteralPath $current.PSPath -Force |
Group-Object -AsHashTable -Property 'PSIsContainer'
$containerCount = $childrenByIsContainer[$true].Count
$leafCount = $childrenByIsContainer[$false].Count
foreach ($childContainer in $childrenByIsContainer[$true])
{
# Store the child container for later processing
$pendingContainers.Enqueue($childContainer)
}
# Write-Output is superfluous here, though makes it explicit that its input will be sent down the pipeline
Write-Output -InputObject (
[PSCustomObject] #{
Name = $current.PSChildName
ContainerCount = $containerCount
LeafCount = $leafCount
TotalCount = $containerCount + $leafCount
}
)
}
# Assign the next container to $current, if available; otherwise, exit the loop
# The second operand to -and works because assigning a non-$null value evaluates to $true
while ($pendingContainers.Count -gt 0 -and ($current = $pendingContainers.Dequeue()))
# For PowerShell (Core) 6+: while ($pendingContainers.TryDequeue([Ref] $current))
Recursive implementation
The above iterative implementation is perhaps more naturally written as a recursive function. The code is a bit shorter and perhaps easier to follow, although one disadvantage is that no results will be output for a given container until all of its descendants have been enumerated, which can cause a noticeable delay in large hierarchies.
function MeasureContainer($container)
{
$containerCount = 0
$leafCount = 0
foreach ($child in Get-ChildItem -LiteralPath $container.PSPath -Force)
{
if ($child.PSIsContainer)
{
$containerCount++
Write-Output -InputObject (
MeasureContainer $child
)
}
else
{
$leafCount++
}
}
Write-Output -InputObject (
[PSCustomObject] #{
Name = $container.PSChildName
ContainerCount = $containerCount
LeafCount = $leafCount
TotalCount = $containerCount + $leafCount
}
)
}
MeasureContainer (Get-Item -Path '.' -Force)
To start the recursion, MeasureContainer is called at the end and passed the base container which, as before, is the current location.
Output
Executing any of the above code blocks will produce output objects like this...
Name
ContainerCount
LeafCount
TotalCount
powershell
1
3
4
linux
0
2
2
...although the order in which they are output depends on the algorithm. You can then manipulate and view them with standard cmdlets such as Select-Object, Sort-Object, Where-Object, etc.
Also, since the code above is written in a provider-agnostic manner ("containers" and "leaves" vs. "directories" and "files") it will work on others types of PSDrives as well; for example, try running...
cd 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\'
...before the above code to see it enumerate and count registry keys (though not values, which are not items but item properties).
Cmdlet implementation
Here's a simple cmdlet that enhances the above iterative code by including each container's absolute path, relative path, and depth in the output object and allows limiting the maximum depth of traversal.
[CmdletBinding()]
param(
[String] $Path = '.', # Default to current location
[ValidateRange(-1, [Int32]::MaxValue)]
[Int32] $MaxDepth = -1 # Default to unlimited depth
)
# Stores the current container being processed; initialize to the base container from parameters
[PSObject] $current = [PSCustomObject] #{
Container = Get-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue
Depth = 0
}
if ($null -eq $current.Container)
{
Write-Error -Message "The object referred to by the base path ""$Path"" could not be found."
}
elseif (-not $current.Container.PSIsContainer)
{
Write-Error -Message "The object referred to by the base path ""$Path"" is not a container."
}
else
{
# Stores containers that have been encountered but not yet processed
# Using a Queue[PSObject] with Enqueue()/Dequeue() results in breadth-first traversal
# Using a Stack[PSObject] with Push()/Pop() results in depth-first traversal
[System.Collections.Generic.Queue[PSObject]] $pendingContainers =
New-Object -TypeName 'System.Collections.Generic.Queue[PSObject]'
do
{
[Int32] $containerCount = 0
[Int32] $leafCount = 0
#TODO: Handle errors due to inaccessible children
foreach ($child in Get-ChildItem -LiteralPath $current.Container.PSPath -Force)
{
if ($child.PSIsContainer)
{
#TODO: Detect and exit directory cycles caused by junctions or symbolic links
# See [System.IO.FileAttributes]::ReparsePoint
$containerCount++
# If the current depth is within the depth limit, or there is no depth limit...
if ($current.Depth -lt $MaxDepth -or $MaxDepth -eq -1)
{
# Store the child container for later processing
$pendingContainers.Enqueue(
[PSCustomObject] #{
Container = $child
Depth = $current.Depth + 1
}
)
}
}
else
{
$leafCount++
}
}
# Write-Output is superfluous here, though makes it explicit that its input will be sent down the pipeline
Write-Output -InputObject (
[PSCustomObject] #{
# Display a "friendly" provider-specific path instead
AbsolutePath = Convert-Path -LiteralPath $current.Container.PSPath
RelativePath = if ($current.Depth -eq 0) {
# Resolve-Path ... -Relative returns a path prefixed with ".." when
# passed the current location; substitute a less roundabout value instead
'.'
} else {
# Resolve-Path ... -Relative returns a path relative to the current
# location and doesn't allow another base location to be specified, so
# the location must changed before and reverted after resolution. Bleh.
Push-Location -LiteralPath $Path
try
{
Resolve-Path -LiteralPath $current.Container.PSPath -Relative
}
finally
{
Pop-Location
}
}
Name = $current.Container.PSChildName
Depth = $current.Depth
ContainerCount = $containerCount
LeafCount = $leafCount
TotalCount = $containerCount + $leafCount
}
)
}
# Assign the next container to $current, if available; otherwise, exit the loop
# The second operand to -and works because assigning a non-$null value evaluates to $true
while ($pendingContainers.Count -gt 0 -and ($current = $pendingContainers.Dequeue()))
# For PowerShell (Core) 6+: while ($pendingContainers.TryDequeue([Ref] $current))
}
Output
When running...
.\SO71470092.ps1 -Path $PSHOME | Select-Object -First 25
...I get these results on Windows Powershell 5.1.19041.1320...
AbsolutePath
RelativePath
Name
Depth
ContainerCount
LeafCount
TotalCount
C:\Windows\System32\WindowsPowerShell\v1.0
.
v1.0
0
6
29
35
C:\Windows\System32\WindowsPowerShell\v1.0\en
.\en
en
1
0
1
1
C:\Windows\System32\WindowsPowerShell\v1.0\en-US
.\en-US
en-US
1
0
271
271
C:\Windows\System32\WindowsPowerShell\v1.0\Examples
.\Examples
Examples
1
0
1
1
C:\Windows\System32\WindowsPowerShell\v1.0\Modules
.\Modules
Modules
1
75
0
75
C:\Windows\System32\WindowsPowerShell\v1.0\Schemas
.\Schemas
Schemas
1
1
0
1
C:\Windows\System32\WindowsPowerShell\v1.0\SessionConfig
.\SessionConfig
SessionConfig
1
0
0
0
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\AppBackgroundTask
.\Modules\AppBackgroundTask
AppBackgroundTask
2
1
5
6
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\AppLocker
.\Modules\AppLocker
AppLocker
2
1
2
3
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\AppvClient
.\Modules\AppvClient
AppvClient
2
2
7
9
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Appx
.\Modules\Appx
Appx
2
1
4
5
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\AssignedAccess
.\Modules\AssignedAccess
AssignedAccess
2
1
3
4
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\BitLocker
.\Modules\BitLocker
BitLocker
2
1
5
6
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\BitsTransfer
.\Modules\BitsTransfer
BitsTransfer
2
1
4
5
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\BranchCache
.\Modules\BranchCache
BranchCache
2
1
13
14
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\CimCmdlets
.\Modules\CimCmdlets
CimCmdlets
2
1
2
3
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\ConfigCI
.\Modules\ConfigCI
ConfigCI
2
1
2
3
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Defender
.\Modules\Defender
Defender
2
1
10
11
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\DeliveryOptimization
.\Modules\DeliveryOptimization
DeliveryOptimization
2
0
4
4
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\DirectAccessClientComponents
.\Modules\DirectAccessClientComponents
DirectAccessClientComponents
2
1
8
9
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Dism
.\Modules\Dism
Dism
2
2
6
8
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\DnsClient
.\Modules\DnsClient
DnsClient
2
1
16
17
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\EventTracingManagement
.\Modules\EventTracingManagement
EventTracingManagement
2
1
10
11
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\International
.\Modules\International
International
2
1
2
3
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\iSCSI
.\Modules\iSCSI
iSCSI
2
1
6
7

Windows PowerShell: How to parse the log file?

I have an input file with below contents:
27/08/2020 02:47:37.365 (-0516) hostname12 ult_licesrv ULT 5 LiceSrv Main[108 00000 Session 'session1' (from 'vmpms1\app1#pmc21app20.pm.com') request for 1 additional licenses for module 'SA-XT' - 1 licenses have been allocated by concurrent usage category 'Unlimited' (session module usage now 1, session category usage now 1, total module concurrent usage now 1, total category usage now 1)
27/08/2020 02:47:37.600 (-0516) hostname13 ult_licesrv ULT 5 LiceSrv Main[108 00000 Session 'sssion2' (from 'vmpms2\app1#pmc21app20.pm.com') request for 1 additional licenses for module 'SA-XT-Read' - 1 licenses have been allocated by concurrent usage category 'Floating' (session module usage now 2, session category usage now 2, total module concurrent usage now 1, total category usage now 1)
27/08/2020 02:47:37.115 (-0516) hostname141 ult_licesrv CMN 5 Logging Housekee 00000 Deleting old log file 'C:\Program Files\PMCOM Global\License Server\diag_ult_licesrv_20200824_011130.log.gz' as it exceeds the purge threashold of 72 hours
27/08/2020 02:47:37.115 (-0516) hostname141 ult_licesrv CMN 5 Logging Housekee 00000 Deleting old log file 'C:\Program Files\PMCOM Global\License Server\diag_ult_licesrv_20200824_021310.log.gz' as it exceeds the purge threashold of 72 hours
27/08/2020 02:47:37.625 (-0516) hostname150 ult_licesrv ULT 5 LiceSrv Main[108 00000 Session 'session1' (from 'vmpms1\app1#pmc21app20.pm.com') request for 1 additional licenses for module 'SA-XT' - 1 licenses have been allocated by concurrent usage category 'Unlimited' (session module usage now 2, session category usage now 1, total module concurrent usage now 2, total category usage now 1)
I need to generate and output file like below:
Date,time,hostname,session_module_usage,session_category_usage,module_concurrent_usage,total_category_usage
27/08/2020,02:47:37.365 (-0516),hostname12,1,1,1,1
27/08/2020,02:47:37.600 (-0516),hostname13,2,2,1,1
27/08/2020,02:47:37.115 (-0516),hostname141,0,0,0,0
27/08/2020,02:47:37.115 (-0516),hostname141,0,0,0,0
27/08/2020,02:47:37.625 (-0516),hostname150,2,1,2,1
The output data order is: Date,time,hostname,session_module_usage,session_category_usage,module_concurrent_usage,total_category_usage.
Put 0,0,0,0 if no entry for session_module_usage,session_category_usage,module_concurrent_usage,total_category_usage
I need to get content from the input file and write the output to another file.
Update
I have created a file input.txt in F drive and pasted the log details into it.
Then I form an array by splitting the file content when a new line occurs like below.
$myList = (Get-Content -Path F:\input.txt) -split '\n'
Now I got 5 items in my array myList. Then I replace the multiple blank spaces with a single blank space and formed a new array by splitting each element by blank space. Then I print the 0 to 3 array elements. Now I need to add the end values (session_module_usage,session_category_usage,module_concurrent_usage,total_category_usage).
PS C:\Users\user> $myList = (Get-Content -Path F:\input.txt) -split '\n'
PS C:\Users\user> $myList.Length
5
PS C:\Users\user> $myList = (Get-Content -Path F:\input.txt) -split '\n'
PS C:\Users\user> $myList.Length
5
PS C:\Users\user> for ($i = 0; $i -le ($myList.length - 1); $i += 1) {
>> $newList = ($myList[$i] -replace '\s+', ' ') -split ' '
>> $newList[0]+','+$newList[1]+' '+$newList[2]+','+$newList[3]
>> }
27/08/2020,02:47:37.365 (-0516),hostname12
27/08/2020,02:47:37.600 (-0516),hostname13
27/08/2020,02:47:37.115 (-0516),hostname141
27/08/2020,02:47:37.115 (-0516),hostname141
27/08/2020,02:47:37.625 (-0516),hostname150
If you really need to filter on the granularity that you're looking for, then you may need to use regex to filter the lines.
This would assume that the rows have similarly labeled lines before the values you're looking for, so keep that in mind.
[System.Collections.ArrayList]$filteredRows = #()
$log = Get-Content -Path C:\logfile.log
foreach ($row in $log) {
$rowIndex = $log.IndexOf($row)
$date = ([regex]::Match($log[$rowIndex],'^\d+\/\d+\/\d+')).value
$time = ([regex]::Match($log[$rowIndex],'\d+:\d+:\d+\.\d+\s\(\S+\)')).value
$hostname = ([regex]::Match($log[$rowIndex],'(?<=\d\d\d\d\) )\w+')).value
$sessionModuleUsage = ([regex]::Match($log[$rowIndex],'(?<=session module usage now )\d')).value
if (!$sessionModuleUsage) {
$sessionModuleUsage = 0
}
$sessionCategoryUsage = ([regex]::Match($log[$rowIndex],'(?<=session category usage now )\d')).value
if (!$sessionCategoryUsage) {
$sessionCategoryUsage = 0
}
$moduleConcurrentUsage = ([regex]::Match($log[$rowIndex],'(?<=total module concurrent usage now )\d')).value
if (!$moduleConcurrentUsage) {
$moduleConcurrentUsage = 0
}
$totalCategoryUsage = ([regex]::Match($log[$rowIndex],'(?<=total category usage now )\d')).value
if (!$totalCategoryUsage) {
$totalCategoryUsage = 0
}
$hash = [ordered]#{
Date = $date
time = $time
hostname = $hostname
session_module_usage = $sessionModuleUsage
session_category_usage = $sessionCategoryUsage
module_concurrent_usage = $moduleConcurrentUsage
total_category_usage = $totalCategoryUsage
}
$rowData = New-Object -TypeName 'psobject' -Property $hash
$filteredRows.Add($rowData) > $null
}
$csv = $filteredRows | convertto-csv -NoTypeInformation -Delimiter "," | foreach {$_ -replace '"',''}
$csv | Out-File C:\results.csv
What essentially needs to happen is that we need to get-content of the log, which returns an array with each item terminated on a newline.
Once we have the rows, we need to grab the values via regex
Since you want zeroes in some of the items if those values don't exist, I have if statements that assign '0' if the regex returns nothing
Finally, we add each filtered item to a PSObject and append that object to an array of objects in each iteration.
Then export to a CSV.
You can probably pick apart the lines with a regex and substrings easily enough. Basically something like the following:
# Iterate over the lines of the input file
Get-Content F:\input.txt |
ForEach-Object {
# Extract the individual fields
$Date = $_.Substring(0, 10)
$Time = $_.Substring(12, $_.IndexOf(')') - 11)
$Hostname = $_.Substring(34, $_.IndexOf(' ', 34) - 34)
$session_module_usage = 0
$session_category_usage = 0
$module_concurrent_usage = 0
$total_category_usage = 0
if ($_ -match 'session module usage now (\d+), session category usage now (\d+), total module concurrent usage now (\d+), total category usage now (\d+)') {
$session_module_usage = $Matches[1]
$session_category_usage = $Matches[2]
$module_concurrent_usage = $Matches[3]
$total_category_usage = $Matches[4]
}
# Create custom object with those properties
New-Object PSObject -Property #{
Date = $Date
time = $Time
hostname = $Hostname
session_module_usage = $session_module_usage
session_category_usage = $session_category_usage
module_concurrent_usage = $module_concurrent_usage
total_category_usage = $total_category_usage
}
} |
# Ensure column order in output
Select-Object Date,time,hostname,session_module_usage,session_category_usage,module_concurrent_usage,total_category_usage |
# Write as CSV - without quotes
ConvertTo-Csv -NoTypeInformation |
ForEach-Object { $_ -replace '"' } |
Out-File F:\output.csv
Whether to pull the date, time, and host name from the line with substrings or regex is probably a matter of taste. Same goes for how strict the format must be matched, but that to me mostly depends on how rigid the format is. For more free-form things where different lines would match different regexes, or multiple lines makes up a single record, I also quite like switch -Regex to iterate over the lines.

Powershell For Each Variable By Number

I have the following variables:
$testvar_1 = 1, 100
$testvar_2 = 1, 20
$testvar_3 = 0, 260
$testvar_4 = 1, 10
How can I make a for loop that will loop through each variable array?
* When you dont know the total number of $testvar_n variables and it changes constantly. *
Something like For Each $testvar_n do { $testvar_n[1] = $testvar_n[1] + 10 }
Sorry very new to powershell.
EDIT:
Perhaps something that can work like the below?
get-variable testvar* | ForEach-Object -Process {$_[1]+10}
You can do something like the following if you need to actually update the existing variables.
foreach ($var in (Get-Variable -Name testvar_[0-9]* -ValueOnly)) {
$var[1] += 10
}
Note: [0-9] is the wildcard range operator for any single digit. The wildcard character * matches zero or more characters, which could match testvar_1abc for example. You can further prevent that with a Where-Object condition if necessary. See below for further filtering.
$vars = Get-Variable -Name testvar_[0-9]* | Where Name -match '^testvar_\d+$'
foreach ($var in $vars) {
$var.Value[1] += 10
}

Powershell script exits ForEach-Object loop prematurely [duplicate]

This question already has answers here:
Why does 'continue' behave like 'break' in a Foreach-Object?
(4 answers)
Closed 5 years ago.
So I've been writing a script that will take all of the data that is stored in 238 spreadsheets and copy it into a master sheet, as well as 9 high level report sheets. I'm really not sure why, but after a specific document, the script ends prematurely without any errors being posted. It's very strange. I'll post some anonymized code below so maybe someone can help me find the error of my ways here.
As far as I can tell, the document that it exits after is fine. I don't see any data errors in it, and the info is copied successfully to the master document before powershell just calls it quits on the script completely.
I've tried changing the size of the data set by limiting only to the folder that contains the problem file. It still ends after the same file with no error output. I cannot upload the file due to company policy, but I really don't see anything different about the data on that one file when compared to any other file of the same nature.
Also, apologies in advance for the crappy code. I'm not a developer and have been relearning powershell since it's the only tool available to me right now.
$StartTime = Get-Date -Format g
Write-Host $StartTime
pushd "Z:\Shared Documents\IO"
$TrackTemplate = "C:\Users\USERNAME\Desktop\IODATA\MasterTemplate.xlsx"
# Initialize the Master Spreadsheet
$xlMaster = New-Object -ComObject Excel.Application
$xlMaster.Visible = $False
$xlMaster.DisplayAlerts = $False
$MasterFilePath = "C:\Users\USERNAME\Desktop\IODATA\Master.xlsx"
Copy-Item $TrackTemplate $MasterFilePath
$wbMaster = $xlMaster.Workbooks.Open($MasterFilePath)
$wsMaster = $wbMaster.Worksheets.Item(2)
$wsMaster.Unprotect("PASSWORD")
$wsMasterRow = 3
# Initialize L4 Document Object
$xlL4 = New-Object -ComObject Excel.Application
$xlL4.Visible = $False
$xlL4.DisplayAlerts = $False
# Initialize object for input documents
$xlInput = New-Object -ComObject Excel.Application
$xlInput.Visible = $False
$xlInput.DisplayAlerts = $False
# Arrays used to create folder path names
$ArrayRoot = #("FOLDER1","FOLDER2","FOLDER3","FOLDER4","FOLDER5","FOLDER6","FOLDER7","FOLDER8","FOLDER9")
$ArrayShort = #("SUB1","SUB2","SUB3","SUB4","SUB5","SUB6","SUB7","SUB8","SUB9")
# $counter is used to iterate inside the loop over the short name array.
$counter = 0
$FileNumber = 0
$TotalFiles = 238
$ArrayRoot | ForEach-Object {
$FilePathL4 = "C:\Users\USERNAME\Desktop\IODATA\ROLLUP\" + $ArrayShort[$counter] + "_DOC_ROLLUP.xlsx"
Copy-Item $TrackTemplate $FilePathL4
$wbL4 = $xlL4.Workbooks.Open($FilePathL4)
$wsL4 = $wbL4.Worksheets.Item(2)
$wsL4.Unprotect("PASSWORD")
$wsL4Row = 3
If ($ArrayShort[$counter] -eq "SUB7") {$FilePath = "Z:\Shared Documents\IO\" + $_ + "\" + $ArrayShort[$counter] + " - DOC v2\"}
Else {$FilePath = "Z:\Shared Documents\IO\" + $_ + "\!" + $ArrayShort[$counter] + " - DOC v2\"}
Get-ChildItem -Path $FilePath | ForEach-Object {
If ($_.Name -eq "SPECIFIC_DOC.xlsx") {Continue}
$FileNumber += 1
Write-Host "$FileNumber / $TotalFiles $_"
$wbInput = $xlInput.Workbooks.Open($_.FullName)
$wsInput = $wbInput.Worksheets.Item(2)
$wsInputLastRow = 0
#Find the last row in the Input document
For ($i = 3; $i -le 10000; $i++) {
If ([string]::IsNullOrEmpty($wsInput.Cells.Item($i,1).Value2)) {
$wsInputLastRow = $i - 1
Break
}
Else { Continue }
}
[void]$wsInput.Range("A3:AC$wsInputLastRow").Copy()
Start-Sleep -Seconds 1
[void]$wsMaster.Range("A$wsMasterRow").PasteSpecial(-4163)
Start-Sleep -Seconds 1
$wsMasterRow += $wsInputLastRow - 2
[void]$wsL4.Range("A$wsL4Row").PasteSpecial(-4163)
Start-Sleep -Seconds 1
$wsL4Row += $wsInputLastRow - 2
$wbInput.Close()
$wbMaster.Save()
}
$counter += 1
$wsL4.Protect("PASSWORD")
$wbL4.Save()
$wbL4.Close()
}
$wsMaster.Protect("PASSWORD")
$wbMaster.Save()
$wbMaster.Close()
$xlMaster.Quit()
$EndTime = Get-Date -Format g
$TimeTotal = New-Timespan -Start $StartTime -End $EndTime
Write-Host $TimeTotal
To continue pipeline processing with the next input object, use return - not continue - in the script block passed to the ForEach-Object cmdlet.
The following simple example skips the 1st object output by Get-ChildItem and passes the remaining ones through:
$i = 0; Get-ChildItem | ForEach-Object{ if ($i++ -eq 0) { return }; $_ }
There is currently (PSv5.1) no direct way to stop the processing of further input objects - for workarounds, see this answer of mine.
By contrast, as you've discovered, break and continue only work as expected in the script block of a for / foreach statement, not directly in the script block passed to the ForeEach-Object cmdlet:
For instance, the following produces no output (using break would have the same effect):
$i = 0; Get-ChildItem | ForEach-Object{ if ($i++ -eq 0) { continue }; $_ }
The reason is that continue and break look for an enclosing for / foreach statement to continue / break out of, and since there is none, the entire command is exited; in a script, the entire script is exited if there's no enclosing for / foreach / switch statement on the call stack.

Powershell adding numbers in a loop

I have the below code which is meant to total up the time offset as the loop rotates (I will then need to divide this by 10 to get the average but first I need to get this bit working).
I'm assuming I need to cast something as [INT] but I've tried multiple locations that would make sense to no avail. I just end up with O's.
$winTimeStripchart = w32tm /stripchart /computer:0.pool.ntp.org /dataonly /samples:10
$WinTimeOffset = $null
For($i=3; $i -le 12; $i++){
$Offset = $winTimeStripchart[$i].split("-")
$trimmedOffset = $Offset[1].trim("s")
$winTimeOffset = $winTimeOffset + $trimmedOffset
}
Write-Host "Total: $winTimeOffset"
# Now need to divide by 10.
sample data:
20:30:23, -00.0698082s
20:30:25, -00.0704645s
20:30:27, -00.0708694s
20:30:29, -00.0728990s
20:30:31, -00.0719226s
20:30:33, -00.0749031s
20:30:36, -00.0778656s
20:30:38, -00.0782183s
20:30:40, -00.0752974s
20:30:42, -00.0760958s
You can try this one line command :
$a = w32tm /stripchart /computer:0.fr.pool.ntp.org /dataonly /samples:10 | select -Skip 3 | % {[double]$_.Substring(11,10)} | Measure-Object -Average -sum
$a can also give you maximum and minimum adding Measure-Object params.
You'll want to cast to a double rather than int. The following should do it:
[System.Collections.ArrayList]$winTimeStripchart = w32tm /stripchart /computer:0.pool.ntp.org /dataonly /samples:10
$WinTimeOffset = $null
$winTimeStripchart.RemoveRange(0,3)
foreach($entry in $winTimeStripchart){
$Offset = $entry.split("-")
$trimmedOffset = $Offset[1].trim("s")
$winTimeOffset = [double]$winTimeOffset + [double]$trimmedOffset
}
Write-Host "Total: $winTimeOffset"
Write-Host "Average: $($winTimeOffset/$winTimeStripchart.count)"
Sample output:
Total: 6.1581437
Average: 0.61581437
I've make some other tweaks to the script as well to make it more scalable:
Foreach rather than a for loop
Using a list rather than array and stripping first 3 entries.
Dividing buy number of entries in list.
regards
Arcas