Powershell - Script not entering for each loop - shell

I have a script set to enter a for each loop every-time a file is created. Once in the loop it will move a file to a another folder and if the same file has to be moved 3 times it will move the file to a different table and remove the record of it from the hash table.
My issue is when I run the script it does not do anything that I write inside the for each loop. Only if I write script above it. Can someone please advise?
$folder = 'C:\Users\jnwankwo\Documents\IUR Test\r' # Enter the root path you want to monitor.
$Failedfolder = 'C:\Users\jnwankwo\Documents\IUR Test\r'
$filter = '*.*' # You can enter a wildcard filter here.
$Files = #{}
$Counter = 1
$folder = 'C:\Users\jnwankwo\Documents\IUR Test\r' # Enter the root path you want to monitor.
$Failedfolder = 'C:\Users\jnwankwo\Documents\IUR Test\r'
$filter = '*.*' # You can enter a wildcard filter here.
$Files = #{}
$Counter = 1
# In the following line, you can change 'IncludeSubdirectories to $true if required.
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
ForEach ($file in $folder)
{
$fName = $file.Name
if (-not $Files.ContainsKey($fName))
{
$Files.Add($fName,$Counter)
}
if (($Files.ContainsKey($fName)) -and ($Files.Item($fName) -lt 3))
{
Move-Item 'C:\Users\jnwankwo\Documents\IUR Test\r\*.txt' 'C:\Users\jnwankwo\Documents\IUR Test' -force
$Files.Set_Item($fName,$Counter++)
}
ElseIf (($Files.ContainsKey($fName)) -and ($Files.Item($fName) -eq 3))
{
$Files.clear()
Move-Item 'C:\Users\jnwankwo\Documents\Failed\' $Failedfolder -force
}
}
}
# To stop the monitoring, run the following commands:
# Unregister-Event FileCreated

I have found one thing in your code.
Change ForEach ($file in $folder) to ForEach ($file in (gci $folder))

Here you go, you will have to change the folders back though :)
$folder = 'C:\temp\test' # Enter the root path you want to monitor.
$filter = '*.*' # You can enter a wildcard filter here.
# In the following line, you can change 'IncludeSubdirectories to $true if required.
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName,LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
$folder = 'C:\temp\test' # Enter the root path you want to monitor.
$Failedfolder = 'C:\temp\test3'
$Files = #{}
$Counter = 1
ForEach ($file in (gci $folder))
{
$fName = $file.Name
if (-not $Files.ContainsKey($fName))
{
$Files.Add($fName,$Counter)
}
if (($Files.ContainsKey($fName)) -and ($Files.Item($fName) -lt 3))
{
Move-Item $file.Fullname 'C:\Users\jnwankwo\Documents\IUR Test' -force
$Files.Item($fName) = $Counter++
}
ElseIf (($Files.ContainsKey($fName)) -and ($Files.Item($fName) -eq 3))
{
$Files.clear()
Move-Item $file.Fullname $Failedfolder -force
}
}
}
Addition:
To store your Hashtable to a file and re-import it on the next run you can use the following code:
#store hashtable to file
$Files.GetEnumerator() | % { "$($_.Name)=$($_.Value)" } | Out-File files_ht.txt
#to import again
$Files = Get-Content files_ht.txt | Convertfrom-StringData
This should enable you to have the data from the hashtable persistent

Related

Is there a robocopy switch for gathering timestamps longer than 260 characters?

I am attempting to extract the date last modified from the files in a Windows directory. Here is my basic script:
Function Get-FolderItem {
[cmdletbinding(DefaultParameterSetName='Filter')]
Param (
[parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName')]
[string[]]$Path = $PWD,
[parameter(ParameterSetName='Filter')]
[string[]]$Filter = '*.*',
[parameter(ParameterSetName='Exclude')]
[string[]]$ExcludeFile,
[parameter()]
[int]$MaxAge,
[parameter()]
[int]$MinAge
)
Begin {
$params = New-Object System.Collections.Arraylist
$params.AddRange(#("/L","/E","/NJH","/NDL","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W","/TS","/UNILOG:c:\temp\test.txt"))
#params.AddRange(#("/L","/S","/NJH","/BYTES","/FP","/NC","/NDL","/TS","/XJ","/R:0","/W:0"))
If ($PSBoundParameters['MaxAge']) {
$params.Add("/MaxAge:$MaxAge") | Out-Null
}
If ($PSBoundParameters['MinAge']) {
$params.Add("/MinAge:$MinAge") | Out-Null
}
}
Process {
ForEach ($item in $Path) {
Try {
$item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop)) {
Write-Warning ("{0} is not a directory and will be skipped" -f $item)
Return
}
If ($PSBoundParameters['ExcludeFile']) {
$Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile -join ',')"
} Else {
$Script = "robocopy `"$item`" NULL $Filter $params"
}
Write-Verbose ("Scanning {0}" -f $item)
Invoke-Expression $Script | Out-Null
get-content "c:\temp\test.txt" | ForEach {
Try {
If ($_.Trim() -match "^(?<Children>\d+)\s(?<FullName>.*)") {
$object = New-Object PSObject -Property #{
FullName = $matches.FullName
Extension = $matches.fullname -replace '.*\.(.*)','$1'
FullPathLength = [int] $matches.FullName.Length
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Created = ([System.IO.FileInfo] $matches.FullName).creationtime
LastWriteTime = ([System.IO.FileInfo] $matches.FullName).LastWriteTime
Characters = (Get-Content -LiteralPath "\\?\$($matches.FullName)" | Measure-Object -ignorewhitespace -Character).Characters
Owner = (Get-ACL $matches.Fullname).Owner
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
} Else {
Write-Verbose ("Not matched: {0}" -f $_)
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
}
$a = Get-FolderItem "C:\TargetDirectory\Folder" | Export-Csv -Path C:\Temp\output.csv -Encoding Unicode
The script extracts the date last modified of filepaths less than 260 characters. It returns a nonsense date of 1600-12-31 4:00:00 PM for files longer than 260 characters. Here is the line that is not working:
LastWriteTime = ([System.IO.FileInfo] $matches.FullName).LastWriteTime
My first attempt to solve this problem was to find a command that began with Get- because such commands were useful in extracting filehashes, filepaths, character counts and owner names of files longer than 260 characters. For example:
Owner = (Get-ACL $matches.Fullname).Owner
Characters = (Get-Content -LiteralPath "\\?\$($matches.FullName)" | Measure-Object-ignorewhitespace -Character).Characters
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Get-Date however seemed to be about getting the current date.
In my second attempt, I went back to Boe Prox's original blogpost on this script and noticed that his script had two components that were missing from mine:
a robocopy switch /TS
Date = [datetime]$matches.Date
I added to my script however doing so return an error: WARNING: Cannot convert null to type "System.DateTime". I rechecked the file in the directory, and it clearly has a date.
I reexamined the documentation on Get-Date and tried
Date = Get-Date -Format o | ForEach-Object { $matches -replace ":", "." }
However, this returned WARNING: Cannot convert value "2018/03/05 18:06:54 C:TargetDirectory\Folder\Temp.csv to type "System.IO.FileInfo". Error: " Illegal characters in path."
(N.B. In other posts, people have suggested changing the server settings to permit the existence of files longer than 260 characters. This is not an option for me because I do not have access to the servers.)
Once you hit 260 characters in the path, you hit the old Windows MAX_PATH limitation. In order to get around that, you have to prepend your path with \\?\.
In your code above, you do that for Characters and FileHash but you don't do that when retrieving LastWriteTime. e.g. Changing the path to this will work:
Created = ([System.IO.FileInfo] "\\?\$($matches.FullName)").creationtime
LastWriteTime = ([System.IO.FileInfo] "\\?\$($matches.FullName)").LastWriteTime
The alternative way is to use the Get-ChildItem cmdlet along with \\?\ prepended to the path to retrieve most of the fields you want without having to query it multiple times:
get-content "c:\temp\test.txt" | ForEach {
Try {
If ($_.Trim() -match "^(?<Children>\d+)\s(?<FullName>.*)") {
$file = Get-ChildItem "\\?\$($matches.FullName)"
$object = New-Object PSObject -Property #{
FullName = $file.FullName
Extension = $file.Extension
FullPathLength = $file.FullName.Length
FileHash = Get-FileHash -LiteralPath "\\?\$($matches.FullName)" |Select -Expand Hash
Created = $file.CreationTime
LastWriteTime = $file.LastWriteTime
Characters = (Get-Content -LiteralPath "\\?\$($matches.FullName)" | Measure-Object -ignorewhitespace -Character).Characters
Owner = (Get-ACL $matches.Fullname).Owner
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
} Else {
Write-Verbose ("Not matched: {0}" -f $_)
}
} Catch {
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}

powershell: delete files previously used as argument inside script

I need to periodically scan a folder for new fontfiles to install and delete them afterwards using a powershell script.
During processing I want to skip already installed files and to achieve that I need to resolve the "real" fontname of the provided file.
I figured out everything and it seems to work everything but the file deletion.
The deletion did work until I added the font name resolution using this GlythTypeInterface Object. It seems like the invoked object does "file lock" the fontfile resulting in an UnauthorizedAccessException.
Thats why I tried some garbage collection stuff I found but I can't make it work.
My code so far:
Add-Type -AssemblyName PresentationCore
$FONTS = 0x14
$Path="C:\_fonts_to_install"
$FontItem = Get-Item -Path $Path
$FontList = Get-ChildItem -Path "$FontItem\*" -Include ('*.fon','*.otf','*.ttc','*.ttf')
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.Namespace($FONTS)
$Fontdir = dir $Path
$username = $env:UserName
foreach($File in $FontList) {
$try = $true
$installedFonts = #(Get-ChildItem C:\Users\$username\AppData\Local\Microsoft\Windows\Fonts | Where-Object {$_.PSIsContainer -eq $false} | Select-Object basename)
$fontObject = New-Object -TypeName Windows.Media.GlyphTypeface -ArgumentList $File.fullname
$fontName = $fontObject.Win32FamilyNames.Values
Write-Host $fontName
$fontObject = $null
Remove-Variable fontObject
foreach($font in $installedFonts)
{
if ($font -match $fontName)
{
$try = $false
}
}
if ($try)
{
$objFolder.CopyHere($File.fullname)
}
Write-Host $File
Remove-Item $File -Force -Verbose
}

PowerShell | Need some performance improvement

I have written a powershell script for archival of old log files or say some output file of web application which is in TBs but the script is taking very long time. I have done some improvement but not able to speed up more from here.
Code:
#region Archive Files using 7zip
[cmdletbinding()]
Param
(
[Parameter(Mandatory=$true, HelpMessage = "Path needs to be with trailing slash at the end of location." )]
[string]$SourceFilesPath
)
$7zip = "C:\Program Files\7-Zip\7z.exe"
$FilePath = ""
foreach ( $filename in $(Get-ChildItem $SourceFilesPath -Force -Recurse | where {$_.LastWriteTime -lt (get-date).AddDays(-1).ToShortDateString()}))
{
$FilePath = Get-ItemProperty $filename.FullName
$ZipFilePath = $filename.Directory.ToString() + "\ZippedFiles" + "\Archive_" + $filename.LastWriteTime.ToString("MMddyyyy") + ".7z"
$tempPath = ("-w"+"C:\Temp")
$OutputData = &$7zip a $tempPath -t7z $ZipFilePath $FilePath
$OutputData
if ($OutputData -contains "Everything is OK")
{
Remove-Item $FilePath -Force
Write-Output "File removed $FilePath"
}
Get-Item $ZipFilePath | ForEach-Object {$_.LastWriteTime = $filename.LastWriteTime}
}
#endregion
#region Archive Files using 7zip
[cmdletbinding()]
Param
(
[Parameter(Mandatory = $true, HelpMessage = 'Path needs to be with trailing slash at the end of location.' )]
[string]$SourceFilesPath
)
Import-Module ..\Invoke-Parallel.ps1 # Download from PSGallery
$7zip = 'C:\Program Files\7-Zip\7z.exe'
$fileToArchive = $(Get-ChildItem $SourceFilesPath -Force -Recurse | Where-Object -FilterScript {
$_.LastWriteTime -lt (Get-Date).AddDays(-1).ToShortDateString()
})
$counter = 0
$groupSize = 2000 # Will group items by 2,000 increments
$groups = $fileToArchive | Group-Object -Property {
[math]::Floor($counter++ / $groupSize)
}
$groups
# This will spawn multiple instances of 7zip - depending on how many groups of 2,000 files exist
$groups.Group | Invoke-Parallel -ScriptBlock {
$FilePath = $null
$fileName = $_
$FilePath = Get-ItemProperty -Path $fileName.FullName
$ZipFilePath = $fileName.Directory.ToString() + '\ZippedFiles' + '\Archive_' + $fileName.LastWriteTime.ToString('MMddyyyy') + '.7z'
$tempPath = ('-w'+'C:\Temp')
$OutputData = &$Using:7zip a $tempPath -t7z $ZipFilePath $FilePath
$OutputData
if ($OutputData -contains 'Everything is OK')
{
Remove-Item $FilePath -Force
Write-Output -InputObject "File removed $FilePath"
}
Get-Item $ZipFilePath | ForEach-Object -Process {
$_.LastWriteTime = $fileName.LastWriteTime
}
}

Write new lines to output

I have been trying to create an output file that writes multiple execute scripts taking a certain parameter from an array list. So far I am getting jumbled duplicated output. How can I get one execute command on each line? Here's what I have.
$myArray = #(1,2,3)
foreach ($element in $myArray) {
$myobj = "EXECUTE [masterdb].[dbo].[update_rows] #row_num=" +"'"+$element+"'"+","+ "#status = 'Fail'"
$myprocedure += $myobj
$myobj = $null
}
Out-file -filepath $path -inputobject $myprocedure -width 50 -force
$myprocedure is never initialized as an array, so it becomes a string that you simply add more text to. Either you need to add a linebreak at end of the execute lines:
$myobj = "EXECUTE [masterdb].[dbo].[update_rows] #row_num=" +"'"+$element+"'"+","+ "#status = 'Fail'" + [System.Environment]::NewLine
Or create an empty array called $myprocedure first:
$myArray = #(1,2,3)
$myprocedure = #()
$path = "test.txt"
foreach ($element in $myArray) {
$myobj = "EXECUTE [masterdb].[dbo].[update_rows] #row_num=" +"'"+$element+"'"+","+ "#status = 'Fail'"
$myprocedure += $myobj
$myobj = $null
}
Out-file -filepath $path -inputobject $myprocedure -width 50 -force
Or append 3 times to the file:
$myArray = #(1,2,3)
$path = "test.txt"
#remove-item $path if necessary
foreach ($element in $myArray) {
"EXECUTE [masterdb].[dbo].[update_rows] #row_num=" +"'"+$element+"'"+","+ "#status = 'Fail'" | Out-file -filepath $path -width 50 -force -Append
}

Powershell - Speeding up writing to files

I wrote this script to find all of the folders in a directory and for each folder, check inside a common file if some strings exist and if not add them. I needed to insert strings in particular places. Not really knowing how to do this, I opted for simpler find and replace where the strings needed to be inserted. Anyway this script takes almost an hour to work through 800 files. I'm hoping some experienced members can point out ways to make my task quicker as I have only been working with Powershell for two days. Many Thanks!!!
# First find and replace items.
$FindOne =
$ReplaceOneA =
$ReplaceOneB =
$ReplaceOneC =
# Second find and replace items.
$FindTwo =
$ReplaceTwo =
# Strings to test if exist.
# To avoid duplicate entries.
$PatternOne =
$PatternTwo =
$PatternThree =
$PatternFour =
# Gets window folder names.
$FilePath = "$ProjectPath\$Station\WINDOW"
$Folders = Get-ChildItem $FilePath | Where-Object {$_.mode -match "d"}
# Adds folder names to an array.
$FolderName = #()
$Folders | ForEach-Object { $FolderName += $_.name }
# Adds code to each builder file.
ForEach ($Name in $FolderName) {
$File = "$FilePath\$Name\main.xaml"
$Test = Test-Path $File
# First tests if file exists. If not, no action.
If ($Test -eq $True) {
$StringOne = Select-String -pattern $PatternOne -path $File
$StringTwo = Select-String -pattern $PatternTwo -path $File
$StringThree = Select-String -pattern $PatternThree -path $File
$StringFour = Select-String -pattern $PatternFour -path $File
$Content = Get-Content $File
# If namespaces or object don't exist, add them.
If ($StringOne -eq $null) {
$Content = $Content -Replace $FindOne, $ReplaceOneA
}
If ($StringTwo -eq $null) {
$Content = $Content -Replace $FindOne, $ReplaceOneB
}
If ($StringThree -eq $null) {
$Content = $Content -Replace $FindOne, $ReplaceOneC
}
If ($StringFour -eq $null) {
$Content = $Content -Replace $FindTwo, $ReplaceTwo
}
$Content | Set-Content $File
}
}
# End of program.
You could try writing to the file with a stream, like this
$stream = [System.IO.StreamWriter] $File
$stream.WriteLine($content)
$stream.close()

Resources