<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6520874156912052734</id><updated>2012-01-10T09:10:50.248Z</updated><category term='wma'/><category term='mp3'/><category term='performance'/><category term='ffmpeg'/><category term='cmdlet'/><category term='android'/><category term='powershell'/><category term='python'/><category term='pipes'/><category term='sync'/><category term='perl'/><category term='foobar2000'/><title type='text'>The Ramblings of BSDZ</title><subtitle type='html'>I have developed in a variety of different languages for a number of years. Mostly this is for whoever employs me at the time; however, a substantial amount of my development is for my own models and systems - I will try and share some of these here.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://bsdz-ramblings.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6520874156912052734/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://bsdz-ramblings.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Blair S</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>2</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6520874156912052734.post-9146076774800865588</id><published>2010-06-19T20:18:00.009+01:00</published><updated>2010-06-20T11:06:04.298+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sync'/><category scheme='http://www.blogger.com/atom/ns#' term='ffmpeg'/><category scheme='http://www.blogger.com/atom/ns#' term='wma'/><category scheme='http://www.blogger.com/atom/ns#' term='foobar2000'/><category scheme='http://www.blogger.com/atom/ns#' term='mp3'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='android'/><title type='text'>Synchronize your Foobar2000 playlists with your Android device (or any other MP3 player)</title><content type='html'>I recently decided I wanted to synchronize some of my music with my Nexus One. I quickly discovered that the Nexus One didn't support WMA audio files. I also discovered there weren't any out-of-the-box solutions for syncing audio files and playlists with the flexibility I required. &lt;br /&gt;&lt;br /&gt;So I decided to write my own between two World Cup matches. I felt the script might be useful to other people and I couldn't find a suitable place to put it. So I am dumping it here.&lt;br /&gt;&lt;br /&gt;You will need the following software installed to use this script.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.foobar2000.org/download"&gt;Foobar2000&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.hydrogenaudio.org/forums/index.php?showtopic=39946"&gt;Com Automation Server for Foobar2000&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.python.org/download/"&gt;Python 2.6.*&lt;/a&gt;&lt;br /&gt;&lt;a href="http://sourceforge.net/projects/pywin32/"&gt;Win32 Extensions for Python&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.videohelp.com/tools/ffmpeg"&gt;FFmpeg&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;All my music files are currently in WMA format. This will change in the future but until then the script converts the files into MP3 using FFmpeg. This can be changed if you need by adjusting the function "convert_file".&lt;br /&gt;&lt;br /&gt;Copy and paste the code below into a file called syncplayer.py. You may need to edit some of the settings at the top of the file. If you have Python files correctly associated on your PC then you should be able to doubleclick the file and the sync will run whilst displaying a log on the console.&lt;br /&gt;&lt;br /&gt;Enjoy!&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;code&gt;&lt;br /&gt;"""&lt;br /&gt;    SyncPlayer.py - A Python script for synchronizing music files between &lt;br /&gt;    Foobar2000 and an MP3 device.&lt;br /&gt;&lt;br /&gt;    Copyright (C) 2010 Blair Sutton&lt;br /&gt;&lt;br /&gt;    This program is free software: you can redistribute it and/or modify&lt;br /&gt;    it under the terms of the GNU General Public License as published by&lt;br /&gt;    the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;    (at your option) any later version.&lt;br /&gt;&lt;br /&gt;    This program is distributed in the hope that it will be useful,&lt;br /&gt;    but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;    GNU General Public License for more details.&lt;br /&gt;&lt;br /&gt;    You should have received a copy of the GNU General Public License&lt;br /&gt;    along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.  &lt;br /&gt;"""&lt;br /&gt;&lt;br /&gt;########################&lt;br /&gt;#### SETTINGS BELOW ####&lt;br /&gt;########################&lt;br /&gt;&lt;br /&gt;# this is an array of foobar2000 playlist names you want synched&lt;br /&gt;#&lt;br /&gt;playlists = [ "NexusOne", "Driving Music" ]  &lt;br /&gt;&lt;br /&gt;# this is the path to your android/mp3 player music folder once mounted.&lt;br /&gt;# the converted files will be placed here.&lt;br /&gt;#&lt;br /&gt;destination_root = r"f:\Music"&lt;br /&gt;&lt;br /&gt;# this is the path to your android/mp3 player playlist folder. m3u files will&lt;br /&gt;# place here.&lt;br /&gt;#&lt;br /&gt;playlist_root = r"f:\Music\Playlists"&lt;br /&gt;&lt;br /&gt;# this is your target conversion format.&lt;br /&gt;#&lt;br /&gt;destination_ext = ".mp3"&lt;br /&gt;&lt;br /&gt;# this is how many path levels including your source audio file are synced to the&lt;br /&gt;# device. i.e. artist/album/audio file&lt;br /&gt;# &lt;br /&gt;path_depth = 3  &lt;br /&gt;&lt;br /&gt;# change these paths to reflect where your converters are installed&lt;br /&gt;#&lt;br /&gt;ffmpeg_path = r"C:\Program Files\FFmpeg-0.6-svn-23607\bin\ffmpeg.exe"&lt;br /&gt;&lt;br /&gt;####################&lt;br /&gt;#### CODE BELOW ####&lt;br /&gt;####################&lt;br /&gt;&lt;br /&gt;tag_list = ["Author","Title","UserRating","UserServiceRating","WM/AlbumArtist","WM/AlbumTitle",&lt;br /&gt;"WM/Category","WM/Composer","WM/Conductor","WM/ContentDistributor","WM/ContentGroupDescription",&lt;br /&gt;"WM/EncodingTime","WM/Genre","WM/GenreID","WM/InitialKey","WM/Language","WM/Lyrics","WM/MCDI",&lt;br /&gt;"WM/MediaClassPrimaryID","WM/MediaClassSecondaryID","WM/Mood","WM/ParentalRating","WM/Period",&lt;br /&gt;"WM/ProtectionType","WM/Provider","WM/ProviderRating","WM/ProviderStyle","WM/Publisher",&lt;br /&gt;"WM/SubscriptionContentID","WM/SubTitle","WM/TrackNumber","WM/UniqueFileIdentifier",&lt;br /&gt;"WM/WMCollectionGroupID","WM/WMCollectionID","WM/WMContentID","WM/Writer","WM/Year"]&lt;br /&gt;&lt;br /&gt;from win32com.client.gencache import EnsureDispatch&lt;br /&gt;from os.path import basename, splitext, exists&lt;br /&gt;from os import sep, makedirs&lt;br /&gt;from subprocess import call&lt;br /&gt;from urlparse import urlparse&lt;br /&gt;from logging import StreamHandler, Formatter, DEBUG, INFO, getLogger&lt;br /&gt;&lt;br /&gt;log = getLogger("Foobar.MP3PlayerSync")&lt;br /&gt;log.setLevel(DEBUG)&lt;br /&gt;lh = StreamHandler()&lt;br /&gt;lh.setFormatter(Formatter("%(levelname)s|%(asctime)s|%(filename)s:%(lineno)d|%(message)s"))&lt;br /&gt;log.addHandler(lh)&lt;br /&gt;&lt;br /&gt;log.info("Connecting to foobar2000 COM server...")&lt;br /&gt;&lt;br /&gt;prog_id = "Foobar2000.Application.0.7"&lt;br /&gt;fb2k = EnsureDispatch(prog_id)&lt;br /&gt;&lt;br /&gt;def main():&lt;br /&gt;    fb2k_playlists = [ i for i in fb2k.Playlists if i.Name in playlists ]&lt;br /&gt;    if fb2k_playlists:&lt;br /&gt;        for pl in fb2k_playlists:&lt;br /&gt;            sync_playlist(pl)&lt;br /&gt;    log.info("Completed Sync")&lt;br /&gt;&lt;br /&gt;def sync_playlist(sync_playlist):&lt;br /&gt;    log.info("Syncing playlist '%s'..." % sync_playlist.Name)&lt;br /&gt;    tracks = sync_playlist.GetTracks()&lt;br /&gt;    &lt;br /&gt;    m3u_lines = ["#EXTM3U"]&lt;br /&gt;    for t in tracks: &lt;br /&gt;        m3u_lines.append(t.FormatTitle("#EXTINF:%length_seconds%, %artist% - %title%"))&lt;br /&gt;        source_path = urlparse(t.Path).netloc&lt;br /&gt;        m3u_lines.append(sync_file(source_path))&lt;br /&gt;&lt;br /&gt;    create_m3u(sync_playlist.Name, m3u_lines)&lt;br /&gt;        &lt;br /&gt;def sync_file(source_path):&lt;br /&gt;    parts_all = source_path.split(sep)&lt;br /&gt;    parts = parts_all[-path_depth:]&lt;br /&gt;&lt;br /&gt;    filenameext = parts[path_depth-1]&lt;br /&gt;    (filename, ext) = splitext(filenameext)&lt;br /&gt;   &lt;br /&gt;    parts_new_path = [destination_root]        &lt;br /&gt;    parts_new_path.extend(parts[0:path_depth-1])&lt;br /&gt;    destination_folder = sep.join(parts_new_path)&lt;br /&gt;    parts_new_path.append(filename + destination_ext)     &lt;br /&gt;    destination_path = sep.join(parts_new_path)&lt;br /&gt;    &lt;br /&gt;    if not exists(destination_folder):&lt;br /&gt;        log.info("Creating folder: '%s'..." % destination_folder)&lt;br /&gt;        makedirs(destination_folder)&lt;br /&gt;    &lt;br /&gt;    if not exists(destination_path):&lt;br /&gt;        convert_file(source_path, destination_path)    &lt;br /&gt;    &lt;br /&gt;    return destination_path&lt;br /&gt; &lt;br /&gt;# For my purposes I needed to convert my WMA files (without DRM!) to MP3 so&lt;br /&gt;# my Nexus could understand them. You could skip this bit if your source and&lt;br /&gt;# destination codecs are the same on your devices.&lt;br /&gt;#&lt;br /&gt;def convert_file(input_file, output_file):&lt;br /&gt;    log.info("Synching: '%s' -&gt; '%s'" % (input_file, output_file))&lt;br /&gt;    command = """"%s" -i "%s" -ac 2 -ar 22050 -ab 192k "%s" """ % (ffmpeg_path, input_file, output_file)&lt;br /&gt;    log.debug("Converter command line: '%s'" % command)&lt;br /&gt;    try:&lt;br /&gt;        retcode = call(command, shell=False)&lt;br /&gt;    except OSError, e:&lt;br /&gt;        log.critical("Converter execution failed: ", e)&lt;br /&gt;        &lt;br /&gt;    log.info("Copying media tags over..")    &lt;br /&gt;    wmp = EnsureDispatch("WMPlayer.OCX")&lt;br /&gt;    if_m = wmp.newMedia(input_file)&lt;br /&gt;    of_m = wmp.newMedia(output_file)&lt;br /&gt;    for tag in tag_list:&lt;br /&gt;        if not of_m.isReadOnlyItem(tag) and if_m.getItemInfo(tag) != "":            &lt;br /&gt;            of_m.setItemInfo(tag, if_m.getItemInfo(tag))&lt;br /&gt;&lt;br /&gt;    &lt;br /&gt;def create_m3u(playlist_name, m3u_lines):&lt;br /&gt;    if not exists(playlist_root):&lt;br /&gt;        log.info("Creating folder: '%s'..." % playlist_root)&lt;br /&gt;        makedirs(playlist_root)&lt;br /&gt;        &lt;br /&gt;    m3u_path = "%s\\%s.m3u" % (playlist_root, playlist_name)&lt;br /&gt;    log.info("Creating m3u playlist: '%s'..." % m3u_path)&lt;br /&gt;    f = open(m3u_path, "w+")&lt;br /&gt;    f.write("\n".join(m3u_lines))&lt;br /&gt;    f.close()&lt;br /&gt;    &lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":  &lt;br /&gt;    main()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6520874156912052734-9146076774800865588?l=bsdz-ramblings.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bsdz-ramblings.blogspot.com/feeds/9146076774800865588/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6520874156912052734&amp;postID=9146076774800865588' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6520874156912052734/posts/default/9146076774800865588'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6520874156912052734/posts/default/9146076774800865588'/><link rel='alternate' type='text/html' href='http://bsdz-ramblings.blogspot.com/2010/06/synchronize-your-foobar2000-playlists.html' title='Synchronize your Foobar2000 playlists with your Android device (or any other MP3 player)'/><author><name>Blair S</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6520874156912052734.post-3184790970832543413</id><published>2008-09-05T09:11:00.002+01:00</published><updated>2010-06-20T11:09:03.227+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='powershell'/><category scheme='http://www.blogger.com/atom/ns#' term='cmdlet'/><category scheme='http://www.blogger.com/atom/ns#' term='pipes'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><title type='text'>Powershell Performance</title><content type='html'>&lt;span xmlns=""&gt;&lt;p&gt;Recently, I have grown fond of Powershell. As someone who is also responsible for a little administration from time to time it quickly caught my eye as a language that could solve many mundane problems quickly and succinctly. Having originally come from a UNIX background I could see the Powershell Development Team had taken the best features from Korn Shell and Perl then combined them with the .NET framework to provide a very powerful tool.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;However, it's not all a bed of roses and this will become clear as you read on.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;I have recently been analysing various files containing financial tick data. Typically there are around two million lines in a file and each contains a comma delimited string with a date, time, price and amount traded of a particular stock. For this analysis I needed to extract a single column from this file and save it in a new file. The new file actually being used as input for GNU Octave.&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;The Problem&lt;br /&gt;&lt;/h2&gt;&lt;p&gt;This task is typical and can easily be done with a tool such as Perl or Awk. However, since I am trying to use more Powershell recently, I felt obliged to see how well it could do the job.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;To start, I created a sample data file, testdata.csv, by using the following: -&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;PS&amp;gt; Set-Content -Path testdata.csv -Value ("1/1/2008, 09:00:00, 100, 1`n" * 2000000)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt; What I need to extract is the third column. In each case, I used Measure-Command to also measure script execution time: -&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;PS&amp;gt; Get-Content testdata.csv | % { ($_.Split(","))[2].Trim(" ") } &amp;gt; testdata.out&lt;br /&gt;TotalSeconds      : 1896.8136657&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt; Clearly, I kept busy whilst the command ran but it was initially quite a surprise. One should ask how much of this time is spent on simply sending the data down Powershell's object pipeline?&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;PS&amp;gt; Get-Content testdata.csv &amp;gt; testdata.out&lt;br /&gt;TotalSeconds      : 1292.2079663&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt; So perhaps this is a deficiency of Powershell; one they might improve with V2. Fortunately, in this case we are dealing with a CSV file so we can improve performance using Import-Csv. Here is another attempt: -&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;PS&amp;gt; Set-Content testdata.out (Import-Csv .\testdata.csv -Header D,T,P,V | % { $_.P })&lt;br /&gt;TotalSeconds      : 333.3965842&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;The Perl Way&lt;br /&gt;&lt;/h2&gt;&lt;p&gt;Slightly better but still seems poor and what do you do if your input file is delimited with more than a single character? I thought I should test the same problem using Perl.&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;PS&amp;gt; Copy-Item testdata.csv testdata; perl -nibak -e 's/[ \t]+//g; print \"\".(split(/,/, $_))[2].\"\n\"' testdata.out&lt;br /&gt;TotalSeconds      : 42.8753894&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt; A considerable improvement! Then I began thinking would it be possible to include Perl within the Powershell pipeline. Unfortunately, this is not a simple case of placing Perl after the pipe character '|' since Powershell will not connect to the STDIN and STDOUT of a normal process. One needs to open a stream to STDIN of a Perl process and feed it Powershell's pipeline '$_' object. Furthermore, the STDOUT of the Perl process needs to be collected and sent back through to Powershell as a pipeline object.&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;A Powershell Function&lt;br /&gt;&lt;/h2&gt;&lt;p&gt;My first attempt produced the following Powershell function: -&lt;br /&gt;&lt;/p&gt;&lt;pre  class="prettyprint"&gt;&lt;code&gt;Function Perl-Filter() {&lt;br /&gt;    BEGIN {&lt;br /&gt;        $si = New-Object System.Diagnostics.ProcessStartInfo&lt;br /&gt;        $si.FileName = "C:\perl\bin\perl.exe"&lt;br /&gt;        $si.Arguments = @'&lt;br /&gt;-ne "s/[ \t]+//g; print ''.(split(/,/, $_))[2].\"\n\""&lt;br /&gt;'@&lt;br /&gt;        $si.UseShellExecute = $false&lt;br /&gt;        $si.RedirectStandardOutput = $true&lt;br /&gt;        $si.RedirectStandardInput = $true&lt;br /&gt;        $p = [System.Diagnostics.Process]::Start($si)&lt;br /&gt;    }&lt;br /&gt;    PROCESS {&lt;br /&gt;        $p.StandardInput.WriteLine($_)&lt;br /&gt;        $p.StandardInput.Flush()&lt;br /&gt;    }&lt;br /&gt;    END {&lt;br /&gt;        $p.StandardInput.Close()&lt;br /&gt;        Write-Output $p.StandardOutput.ReadToEnd()&lt;br /&gt;        $p.WaitForExit();&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt; This Powershell function starts by spawning a Perl process and redirecting its STDIN and STDOUT streams. During the processing stage, data is flushed into Perl's STDIN and finally all data from the STDOUT stream is sent back down Powershell's pipeline via the echo command. Note the following will not work: -&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;PS&amp;gt; Get-Content .\testdata.csv | Perl-Filter &amp;gt; testdata.out&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt; One problem with this function is that all of Perl's output is kept in memory until the process ends. It would be nice to Read data from the Perl process during the processing stage and send it down Powershell's pipeline as it is ready. Sadly, due to a &lt;a href="http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=96484"&gt;known problem&lt;/a&gt; with .Net's StreamReader implementation a Read or Peek will block if the Stream has not had any data sent through it. The only workaround I know of is to start a separate thread to manage the Stream and this is where Powershell V1 has its limitations.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Another problem is it simply hangs because Perl is blocked from writing to the STDOUT stream once this pipe buffer is full. This usually is set to around 8KB.&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;A Powershell Cmdlet&lt;br /&gt;&lt;/h2&gt;&lt;p&gt;So, how does one allow a pipeline to access another process's STDIN and STDOUT streams? Well the answer appears to be that one has to write a Cmdlet using C# or VB.Net.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The general layout is similar to that of the function above. One must implement three main methods BeginProcess, ProcessRecord and EndProcess each corresponding to the BEGIN, PROCESS and END blocks above. Building a new Powershell Cmdlet is made very simple by using &lt;a href="http://channel9.msdn.com/playground/Sandbox/249904-Windows-PowerShell-Visual-Studio-2005-Templates-C-and-VBNET/"&gt;David Aiken's Visual Studio Template&lt;/a&gt;.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;I chose to follow a similar structure to the Powershell Function above spawning my process in a begin block but also starting a special thread that monitors the STDOUT of this process. The thread looks for a line delimiter sequence in the stream and as these are discovered records are broken off and pushed into an ObjectQueue. Here is an excerpt from the thread: -&lt;br /&gt;&lt;/p&gt;&lt;pre class="prettyprint"&gt;&lt;code&gt;&lt;br /&gt;    List&amp;lt;char&amp;gt; dataQueue = new List&amp;lt;char&amp;gt;();&lt;br /&gt;    char[] separatorCharArray = Separator.ToCharArray();&lt;br /&gt;    char[] buffer = new char[16 * 1024];&lt;br /&gt;    int totalBytesRead = 0;&lt;br /&gt;    int totalObjectsQueued = 0;&lt;br /&gt;    int bytesRead;&lt;br /&gt;&lt;br /&gt;    while ((bytesRead = p.StandardOutput.Read(buffer, 0, buffer.Length)) &amp;gt; 0)&lt;br /&gt;    {&lt;br /&gt;      totalBytesRead += bytesRead;&lt;br /&gt;&lt;br /&gt;      for (int i = 0; i &amp;lt;= bytesRead - 1; i++)&lt;br /&gt;        dataQueue.Add(buffer[i]);&lt;br /&gt;&lt;br /&gt;      // pump out any complete objects&lt;br /&gt;      bool completeMatch = true;&lt;br /&gt;      int index;&lt;br /&gt;      while ((index = dataQueue.IndexOf(separatorCharArray[0])) &amp;gt; 0)&lt;br /&gt;      {&lt;br /&gt;        // skip if not enough chars to complete match&lt;br /&gt;        if (dataQueue.Count &amp;lt; index + separatorCharArray.Length)&lt;br /&gt;          continue;&lt;br /&gt;&lt;br /&gt;        // check it's a complete match, add if it is&lt;br /&gt;        for (int i = 1; i &amp;lt;= separatorCharArray.Length - 1; i++)&lt;br /&gt;          completeMatch &amp;amp;= dataQueue[index + i] == separatorCharArray[i];&lt;br /&gt;&lt;br /&gt;        if (completeMatch)&lt;br /&gt;        {&lt;br /&gt;          oq.Enqueue(new string(dataQueue.GetRange(0, index).ToArray()));&lt;br /&gt;          totalObjectsQueued++;&lt;br /&gt;          dataQueue.RemoveRange(0, index + separatorCharArray.Length);&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;       &lt;br /&gt;    // enqueue any remaining chars&lt;br /&gt;    if (dataQueue.Count &amp;gt; 0)&lt;br /&gt;    {&lt;br /&gt;      oq.Enqueue(new string(dataQueue.ToArray()));&lt;br /&gt;      dataQueue.Clear();&lt;br /&gt;    }&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The ObjectQueue is then de-queued to the Powershell pipeline during the ProcessRecord and EndRecord stages. Very simply as: -&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;    while (ObjectQueue.Count != 0)&lt;br /&gt;      WriteObject(ObjectQueue.Dequeue());&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt; The advantage of this structure is that one can choose processes that output data in other forms; perhaps even binary files.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Finally measuring the commands output: -&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;PS&amp;gt; Get-Content .\testdata.csv | Get-ProcessPipe -ProcessPath perl.exe -Arguments '-ne "s/\s//g; print q().(split(/,/, $_))[2].qq(\n)" ' &amp;gt; testdata.out&lt;br /&gt;TotalSeconds      : 1683.0998587&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt; This is only a small improvement on the original .Net's string method above.&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;Conclusion&lt;br /&gt;&lt;/h2&gt;&lt;p&gt;There are many advantages to using Powershell for many administration tasks. However, when processing more than 100,000 objects through its pipeline your task's performance will take a big hit. This is perhaps where it is worth falling back on more tried and tested tools such as Perl or Python and using traditional techniques. Powershell's object layer is very powerful; however, it would be nice if it could detect if it was processing either an object stream or text stream and behave accordingly.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;I felt having access to a non-Powershell process as a Cmdlet might be useful so I have posted my code &lt;a href="http://code.google.com/p/bsdz-powershell/"&gt;here on Google&lt;/a&gt; if you would like to play with it. Please note it has not been tested thoroughly and is bound to have many bugs. &lt;/p&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6520874156912052734-3184790970832543413?l=bsdz-ramblings.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bsdz-ramblings.blogspot.com/feeds/3184790970832543413/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6520874156912052734&amp;postID=3184790970832543413' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6520874156912052734/posts/default/3184790970832543413'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6520874156912052734/posts/default/3184790970832543413'/><link rel='alternate' type='text/html' href='http://bsdz-ramblings.blogspot.com/2008/08/powershell-performance.html' title='Powershell Performance'/><author><name>Blair S</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
