none
List TFS Changes By Work Item, User, and/or Date Range RRS feed

  • General discussion

  • Hi All,

    Just wanted to share a PowerShell script I wrote that produces a report on the latest changes for a given work item, user, and/or date range.  I searched high and low for this but couldn't find what I needed anywhere, especially since I wanted to be able to filter by work item specifically, and report on properties upstream and downstream from a ChangeSet, including properties from the work item and change item.  I also needed the list of changes to be unique, and only report the latest check-in date and changeset id.

    It incorporates the add-in for Microsoft Team Foundation Power Tools, and takes advantage of the QueryHistory() cmdlet in the VersionControlServer library in order to relate work items to changes, which I couldn't get using Get-TfsItemHistory() cmdlet.

    Here is the link to the script: https://gallery.technet.microsoft.com/scriptcenter/Export-Foundation-Server-b664c8ce

    Would appreciate some feedback to see if I missed anything or if there's a way to optimize it.  It's pretty rudimentary, and doesn't include any special objects and collections in the Change or Work Items.


    Diane

    Tuesday, October 28, 2014 10:52 PM

All replies

  • Diane,

    Very nice effort.

    Take a look at this and post back if there is anything you do not understand.

    Param(
        [Parameter(Position=0,HelpMessage='TFS Collection Path')]
        [string] $tfsCollectionPath='http://localhost:8080/tfs',
        
        [Parameter(Position=1,HelpMessage='The TFS root path to search from.')]
        [string] $locationToSearch='$/',
    	
        [Parameter(Position=2, HelpMessage='The destination file path.')]
        [string]$outputFile='C:\TFSChanges.csv',
    
        [Parameter(Position=3, HelpMessage="The work item to search for.`nFor all work items: $workItem=''")]
        [string]$workItem,
    
        [Parameter(Position=4, HelpMessage="The user to consider. `nFor all users: $userName=''")]
        [string]$userName,
    
        [Parameter(Position=5, HelpMessage="The start date to consider.`nFor a date/time range: $dateFrom='D2014-10-20T00:00',$dateTo='D2014-10-24T00:00`nFor everything including and after a date/time:$dateFrom='D2014-10-20T00:00', $dateTo='' `nFor all dates: $dateFrom ='', $dateTo=''")]
        [string]$dateFrom,
    
        [Parameter(Position=6,HelpMessage='The end date to consider.')]
        [string]$dateTo
    )

    When you define a variable like this: "[string]$myvar" the default is that the variable is initialized to an eempty ("") string.  THe same is true for all "ValueTypes".  They are initialized as an empty var which is, in most case, a numeric zero and the default always evaluates to $false.

    This is part of the advance OOP design of PosH.

    Instead of typing all of those comments make them the help message.  It serves double duty.


    ¯\_(ツ)_/¯

    Wednesday, October 29, 2014 12:09 AM
  • Hi jrv,

    Thanks for the valuable input, it is much appreciated!

    Just to explain my reasoning, I purposely set the default parameter values to empty strings in order to explicitly demonstrate that default values are allowed to be specified.  That way anyone who would like to use the same script can easily change the defaults for their own purposes.  But it's good to know what the automatic defaults are anyways.

    Never knew about the HelpMessage parameter.  Very cool, but have you read this post: http://powershell.org/wp/2013/05/06/a-helpful-message-about-helpmessage/

    June Blender seems to suggest it's hard to use, and only works for mandatory parameters.  As you can see, none of my parameters are mandatory.  The more preferred way is something like the following:

    <#
    .PARAMETER  tfsCollectionPath
     Specifies the TFS collection path, e.g. "http://localhost:8080/tfs".
    #>
    

    I will take your excellent suggestion to incorporate the help into the script though.  Thanks again!


    Diane

    Wednesday, October 29, 2014 4:54 PM
  • Yes - June is correct.  Using the HELP is best as it is accessible in more ways.

    I was really trying to recommend two things:

    1.   Don't include Mandatory=$false or any other defaults because it makes the code harder to read. 
    2.   Don't over comment the code.  Remmeber the code is being read by someone who knows the technology and notby the end user.

    For supportability less is always more.  Good commenting is an acquired skill.  It takes some thinking about and, perhaps, a little research into what has been written about how to comment code.


    ¯\_(ツ)_/¯

    Wednesday, October 29, 2014 5:04 PM
  • The following lines are redundant and unnecessary:

    if ($userName -eq "" -or $userName -eq $null){
        $userName = $null
    }
    
    if ($dateFrom -eq "" -or $dateFrom -eq $null)
    {	$versionFrom = $null
    }
    else
    {	$versionFrom = [Microsoft.TeamFoundation.VersionControl.Client.VersionSpec]::ParseSingleSpec($dateFrom, "")
    }
    
    if ($dateTo -eq "" -or $dateTo -eq $null)
    {	$versionTo = $null
    }
    else
    {	$versionTo = [Microsoft.TeamFoundation.VersionControl.Client.VersionSpec]::ParseSingleSpec($dateTo, "")
    }

    $null and "" are equivalent.  You are checking for null and setting the same variable to null that you have discovered was null.


    ¯\_(ツ)_/¯

    Wednesday, October 29, 2014 5:17 PM
  • I also see not reason to do any of the following.

    $outputFile = $outputFile.Trim()
    $tfsCollectionPath = $tfsCollectionPath.Trim()
    $locationToSearch = $locationToSearch.Trim()
    $workItem = $workItem.Trim()
    $dateFrom = $dateFrom.Trim()
    $dateTo = $dateTo.Trim()
    $userName = $userName.Trim()
    


    ¯\_(ツ)_/¯

    Wednesday, October 29, 2014 5:18 PM
  • Hi jrv,

    All good points!  I will certainly keep them in mind when writing scripts for myself.

    When I originally wrote this script, my audience was for a bunch of developers in my organization, many of whom have little to no experience in PowerShell, but who needed something they could quickly learn to use to help with their TFS reporting.  This script is a reflection of that thought process, allowing them to quickly get underway and change what they needed, without having to first learn all the ins-and-outs of every command or "google" every line of code.

    I want the "newbie" to be able to understand and use this as easily as the more experienced, even though I totally agree with you that anyone using a script should properly understand all it's commands first, and do the proper research to learn about it.  In my opinion, even though it's a bit more extraneous, spelling things out is more helpful when trying to quickly learn something and get going.  For example, does everyone know that a parameter by default is optional, if it isn't specified?  I certainly didn't when I first began.

    A prime example of this was my research into the VersionControlServer GetHistory() cmdlet!  There is very little in the way of explaining its parameters, how to define them and how to use them, especially the VersionSpec parameters.  There was even less about the resulting collection, how to tie the work item and changes together, and whether you could use properties like $_.Parent to access upstream properties (which you can't).  Would have saved me considerable time had some of these been made more obvious.

    Certainly, the "expert" is welcome to modify the script for themselves and take out all the extraneous pieces, but I think having them in there makes the script more accessible to a general audience.  However, lets just call that a difference in opinion!

    Thanks again for your valuable input!  I really like stimulating this kind of discussion for the whole group to consider.


    Diane

    Wednesday, October 29, 2014 5:43 PM
  • Things like this:

        # The end date to consider.
        [Parameter(Position=6, Mandatory=$false)]
        [string] $dateTo = ""

    Are unnecessary a string in Net is automatically allocated as "". YOu are just redoing what you already asked fro when you created the string.

        
        [Parameter(Position=6)]
        [string]$dateTo  # The end date to consider.

    The following should be handled by validating parameter sets:

    # Check if parameters are too wide and return an error.
    if ($dateFrom -eq "" -and $dateTo -eq "" -and $workItem -eq "" -and $userName -eq "" -and ($locationToSearch -eq "" -or $locationToSearch -eq "$/")){
        Throw "Atleast one of the following parameters must be set: workItem, userName, dateFrom, dateTo, and/or locationToSearch."
    }


    ¯\_(ツ)_/¯

    Wednesday, October 29, 2014 5:57 PM
  • I think when I was testing the script, and before I introduced typed parameters with default values, I found that I was actually getting " " or 0, and not empty-strings if I left out parameters, hence the trim.  Also, wanted to get rid of any extra spaces introduced by using quotes around parameter values.  

    I was also getting some "not defined" messages when I tried to run comparisons, hence the inclusion of the $null comparison, but your right, it is no longer necessary with typed parameters with defaults being specified.  I will remove them the next time I update the script.

    I found that it is necessary to specify $versionFrom and $versionTo as $null, since they are not previously defined anywhere else and hence would generate an "undefined" error when trying to run the QueryHistory() cmdlet.  Remember that they are not string types but VersionSpec types, not the same as the string parameters $dateFrom and $dateTo.  Nor is "" equivalent to $null when trying to use them directly as parameters in the QueryHistory() cmdlet, which also caused an error since it expects VersionSpec parameters and not strings.

    Maybe I'm missing something here?


    Diane

    Wednesday, October 29, 2014 5:58 PM
  • I think when I was testing the script, and before I introduced typed parameters with default values, I found that I was actually getting " " or 0, and not empty-strings if I left out parameters, hence the trim.  Also, wanted to get rid of any extra spaces introduced by using quotes around parameter values.  

    I was also getting some "not defined" messages when I tried to run comparisons, hence the inclusion of the $null comparison, but your right, it is no longer necessary with typed parameters with defaults being specified.  I will remove them the next time I update the script.

    I found that it is necessary to specify $versionFrom and $versionTo as $null, since they are not previously defined anywhere else and hence would generate an "undefined" error when trying to run the QueryHistory() cmdlet.  Remember that they are not string types but VersionSpec types, not the same as the string parameters $dateFrom and $dateTo.  Nor is "" equivalent to $null when trying to use them directly as parameters in the QueryHistory() cmdlet, which also caused an error since it expects VersionSpec parameters and not strings.

    Maybe I'm missing something here?


    Diane

    No - that doesn't happen so you were seeing something else and misinterpreted.  YOur testing is subtly susp[icious.  Usually when a coder writes all of those tests it shows that they do not really understand what is happening and are getting into the coders nightmare which is wring compensation code that only hides the initial errors.

    Once you understand how code works and how the compiler or parser handles the syntax these issues tend to go away.  It is really a matter of trust which comes with experience. Use of the Strict 2.0 is also creating issues as it is not really intended to be used the way you are suing it.  I recommend against it until you completely understand what is happening.  You should also be using version 3 or later semantics on WS2008 and later.  Only un patched versions of Exchange requires V2.

    You are also using some unnecessary selection routines and typing in CmdLets redundantly.  They do nothing but cause no problem.

    Here is one:
    $TargetChangeSetChangeItems | 
        Select-Object |
        Select -Unique WorkItemId, Title, ChangedBy, ServerItem, ChangeSetId, CheckinDate, ChangeType |
        Sort ServerItem, CheckinDate -descending | 
        Group ServerItem |
        % { $_.group[0] } |
        Sort ServerItem |
        Export-Csv -Path $outputFile -UseCulture -Encoding UTF8 -NoTypeInformation

    You have also already defined all of your fields so why redefine them over and over again.

    Some of that statement just doesn't make any sense.  Grouping is a sort.  Grouping also makes the output unusable in a CSV unless you are just asking for a count of items.

    Select -Unique is also a sort/grouping CmdLet.


    ¯\_(ツ)_/¯

    Wednesday, October 29, 2014 6:11 PM
  • Again, this was quick and dirty, and I've never worked with Parameter Set validation.

    I will certainly look into it for next time, because as I understand it, parameter validation does not validate default values, and doesn't work with a combination of parameters.  In my case, I'm trying to validate that atleast one of the parameters is not the default empty string, but the rest of them could be.

    Have a look here for reference: http://blogs.technet.com/b/heyscriptingguy/archive/2011/05/15/simplify-your-powershell-script-with-parameter-validation.aspx

    Of course, that's on first glance.  Anyways, I believe the above does the job I want it to anyways, and I don't really see how that would make the script more optimal.


    Diane

    Wednesday, October 29, 2014 6:12 PM
  • Have you actually altered this to see the result?  It sounds like not because I have, and have found that if I didn't resort the result, it was coming out in CheckinDate descending order, not by server item. 

    The -Unique may be redundant, and I will look to remove it, but the Grouping is completely necessary to get only one row per serveritem.  Try removing it and see for yourself that now every check in from each changeset is shown, resulting in duplicate lines.  I only wanted the most recent changeset.  And by the way, it produced a perfect Excel report for me that includes all of the fields I wanted.

    I am also only specifying some of the properties that I particularly am interested in, but not all I have defined, because this is a generic script, and others may want to include/exclude other fields.

    I may be "lacking in understanding", but believe me, I have researched every line of code in there and not just blindly added it in from a code sample, and aside from the extraneous $null comparison, resulting from a lack of code clean-up afterwards, there was a reason behind every decision made here.

    In our organization, we still have unpatched developer boxes running several OS versions, including even Windows XP and Windows Server 2003 and 2008 (not R2), used for legacy applications.  Hence, the strict versioning is done to enforce the lowest common denominator so it can be run on more platforms.  You seem to be under the impression that this script will only be run server-side by an "expert", when in fact I consider this to be a "developer" tool.

    My original post was asking for missing pieces and for optimization, and you seem to want to nit-pick every single line of the script to see if there was a way rewrite the same thing to get the same result, not necessarily better or faster, just different.  It's comments like the one above that probably stop others from posting their scripts or asking for constructive criticism.

    Since I saw no scripts out there to fulfill the business need I had, I felt it would be beneficial to others if I posted what I came up with.  Sorry if it doesn't meet your exacting standards!  I'll definitely think twice before doing that again!


    Diane

    Wednesday, October 29, 2014 6:35 PM
  • Again, this was quick and dirty, and I've never worked with Parameter Set validation.

    I will certainly look into it for next time, because as I understand it, parameter validation does not validate default values, and doesn't work with a combination of parameters.  In my case, I'm trying to validate that atleast one of the parameters is not the default empty string, but the rest of them could be.

    Have a look here for reference: http://blogs.technet.com/b/heyscriptingguy/archive/2011/05/15/simplify-your-powershell-script-with-parameter-validation.aspx

    Of course, that's on first glance.  Anyways, I believe the above does the job I want it to anyways, and I don't really see how that would make the script more optimal.


    Diane

    Again - you need to learn how to design with these tools.  They are pretty much standard to how any vendor in the industry would implement this type of thing.  It is a logical set of primitives that you use as a toolbox to design a solution.  YOur design is what is in your way.  It iwas driven by a lack of knowledge of how to program and how PowerShell supports the programming effort.  To acquire the knowledge you need to simplify and not stick in things that you do not fully understand.  By taking the simple path and asking correct questions you will magically see how the pices can be used.  It is really a matter of letting your brain do the work. Our choices and personalities are very bad at acquiring logical understanding the first couple of time out.  Once your brain sees what you are feeding it it will help you to "gleen" what is happening.

    You would have to create a set of parameter sets that all require at least one parameter. 

    Anyway - I am notgouing to try to take you through this because there are other things to do first.

    Here are some ideas.  I don't expect you to understand why but look at the restructuring and consider why I might have done it.  The techniques can make coding PowerShell very much easier.

    <#
        .NOTES
        
             Search for all unique file changes in TFS
             for a given date/time range and collection location.
             Write results to a manifest file.
            
             Author: Diane Sithoo
             Created: 2014-10-28
            
             Prior to executing this script, ensure that the script is "Unblocked",
             i.e. Right-click on script, choose Properties, click on "Unblock",
             then click on "OK".
            
    
        .PARAMETER
            For a date/time range: $dateFrom = "D2014-10-20T00:00", $dateTo = "D2014-10-24T00:00" 
            For everything including and after a date/time: $dateFrom = "D2014-10-20T00:00", $dateTo = "" 
            For all dates: $dateFrom = "", $dateTo = ""
        .EXAMPLE
             To run this script from PS As Administrator, type in the following commands in PS:
                C:\Export-TFS-Changes-To-Excel.ps1 -outputFile C:\TFSChanges.csv -workItem <WorkItem> -userName <Domain>\<loginName>
    #>    
    Param(
        [Parameter(Position=0)]
        [string] $tfsCollectionPath='http://localhost:8080/tfs',    # TFS Collection Path
        [Parameter(Position=1)]
        [string] $locationToSearch,                                 # The TFS root path to search from.
        [Parameter(Position=2)]
        [string] $outputFile='C:\TFSChanges.csv',                   # The destination file path.
        [Parameter(Position=3)]
        [string] $workItem,                                         # The work item to search for.
        [Parameter(Position=4)] 
        [string] $userName,                                         # The user to consider.
        [Parameter(Position=5)] 
        [string] $dateFrom,                                         # The start date to consider.
        [Parameter(Position=6)] 
        [string]$dateTo,                                             # The end date to consider.
        $openOutputFile=$true
    )
    
    Begin{
        #Loads Windows PowerShell snap-in if not already loaded
        if(Get-PSSnapin -Name Microsoft.TeamFoundation.PowerShell -ErrorAction SilentlyContinue) {
            Write-Verbose 'TeamFoundation module already loaded'  
        }else{
            Add-PSSnapin Microsoft.TeamFoundation.PowerShell
        }
    }
    
    Process {
        $tfs = get-tfsserver $tfsCollectionPath
        
        if (-not $dateFrom) {
            $versionFrom = [Microsoft.TeamFoundation.VersionControl.Client.VersionSpec]::ParseSingleSpec($dateFrom, "")
        }
        
        if (-not $dateTo) {
            $versionTo = [Microsoft.TeamFoundation.VersionControl.Client.VersionSpec]::ParseSingleSpec($dateTo, "")
        }
        
        $vCS = $tfs.GetService([Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer])
        
        $vCSChangeSets = $vCS.QueryHistory($locationToSearch, [Microsoft.TeamFoundation.VersionControl.Client.VersionSpec]::Latest, 0, 'Full', $userName, $versionFrom, $versionTo, [int32]::MaxValue, $true, $false, $false, $true)
        
        $TargetChangeSetChangeItems = foreach ($vCSChangeSet in $vCSChangeSets) {
            
            foreach ($vCSWorkItem in $vCSChangeSet.WorkItems) {
                
                if ($vCSWorkItem.Id -eq $workItem -or $workItem -eq "") {
                    
                    foreach ($vCSChange in $vCSChangeSet.Changes) {
                        if (
                        $vCSChange.Item.ContentLength -gt 0 -and
                        $vCSChange.Item.ServerItem -notmatch '.DomainTemplate|buildtargets|Intermediate|build.xml|.proj|.publish.xml' -and
                        $vCSChange.ChangeType -notmatch 'Branch|Rename|Merge') {
                            
                            $props = [ordered]@{
                                AreaId = $vCSWorkItem.AreaId
                                AreaPath = $vCSWorkItem.AreaPath
                                AttachedFileCount = $vCSWorkItem.AttachedFileCount
                                ChangedBy = $vCSWorkItem.ChangedBy
                                ChangedDate = $vCSWorkItem.ChangedDate
                                CreatedBy = $vCSWorkItem.CreatedBy
                                CreatedDate = $vCSWorkItem.CreatedDate
                                ExternalLinkCount = $vCSWorkItem.ExternalLinkCount
                                History = $vCSWorkItem.History
                                HyperLinkCount = $vCSWorkItem.HyperLinkCount
                                WorkItemId = $vCSWorkItem.Id
                                IsDirty = $vCSWorkItem.IsDirty
                                IsNew = $vCSWorkItem.IsNew
                                IsOpen = $vCSWorkItem.IsOpen
                                IsPartialOpen = $vCSWorkItem.IsPartialOpen
                                IsReadOnly = $vCSWorkItem.IsReadOnly
                                IsReadOnlyOpen = $vCSWorkItem.IsReadOnlyOpen
                                IterationId = $vCSWorkItem.IterationId
                                IterationPath = $vCSWorkItem.IterationPath
                                NodeName = $vCSWorkItem.NodeName
                                Project = $vCSWorkItem.Project
                                Reason = $vCSWorkItem.Reason
                                RelatedLinkCount = $vCSWorkItem.RelatedLinkCount
                                Rev = $vCSWorkItem.Rev
                                RevisedDate = $vCSWorkItem.RevisedDate
                                Revision = $vCSWorkItem.Revision
                                State = $vCSWorkItem.State
                                Title = $vCSWorkItem.Title
                                ChangeType = $vCSChange.ChangeType
                                ChangeSetId = $vCSItem.ChangeSetId
                                CheckinDate = $vCSItem.CheckinDate
                                ServerItem = $vCSItem.ServerItem
                            }
                            New-Object PsObject -Property $props
                        }
                        
                        if ($vCSWorkItem.Id -eq $workItem) {
                            break
                        }
                    }
                }
            }
        }
    }
    


    ¯\_(ツ)_/¯

    Wednesday, October 29, 2014 7:51 PM
  • Diane - I am not nit picking.  I am just offering so me guidance on how to make this easier and how to avoid unnecessary lines of code.  As a programmer I know that unneeded code is one of the biggest causes of breakage and security issues in a program.  It also makes understanding a script much harder.

    I cannot sort out what you question is.  The resort and regrouping mke it difficult to understand without access to the exact data you are trying to massage.

    I also believe that part of your issue is that the logic in you function has some faulty premises.  You are creating objects from objects that already exist.  Why?

    The following will get you all objects without requiring step-wise logic.

    -

    $vCSChangeSet.Changes |
          Where-Object {
                        $_.Item.ContentLength -and
                        $_.Item.ServerItem -notmatch '.DomainTemplate|buildtargets|Intermediate|build.xml|.proj|.publish.xml' -and
                        $_.ChangeType -notmatch 'Branch|Rename|Merge'
         } 
    

    The output is all objects that match the filter.

    The final reporting phase can just group on the columns of interest.  The collection return fro each group can be referenced to extract the interesting items.

    Anyway - these are some ideas to think about.  DOn';t let me confuse you. YOu are off to a good start. I am curious to see how many things you will use in your next script.

    Good luck and have a great All Hallows Eve.


    ¯\_(ツ)_/¯

    Wednesday, October 29, 2014 8:02 PM
  • Wow, way to take a working script and break it!  It is obvious that you are re-writing code without actually having tested it.  The first rule of "good" programming is that you actually test your work.  Do you not have TFS that you can access?  If you have no test data, then create some and run your own scripts BEFORE releasing them to the wild.  That's just basic coding 101.

    Just for the sake of it, I actually incorporated your changes and tried to run it, and as expected, it failed because, among other things, the VersionSpec parameters versionFrom and versionTo are not instantiated when a user decides they want all dates and leaves them blank:

    Error 1

    Even if I could get by that obvious mistake, because the parameters have not explicitly set default values of "", and have not been passed, they default to " ", not $null, and not "".  By the way this is PowerShell, not .NET as you mentioned earlier, and so no, NULL is not the default value.  A simple write-host statement is all that is necessary to prove it (use any parameter without a default assigned in place of $username if you'd like):

    write-host "'"$userName"'"

    The result I received was ' '.

    Still don't believe me?  Here's another simple test:

    if ($userName -eq $null) {write-host "Value is Null"} else  {write-host "Value is not Null"}

    What did I get?  "Value is not Null".

    So okay, let's say I "get by" these errors by passing in actual dates for my dateFrom and dateTo parameters, and forego the ability to leave them blank.  Let's say I also get rid of the now suddenly removed default valid value for $locationToSearch which has been replaced in your version with the non-valid value of " ", and pass in a valid value "$/", which was causing the last error above. 

    So now I try running your script with these parameters now:

    C:\PowerShell\Export-TFS-Changes-To-Excel-Public1.ps1 -outputFile C:\PowerShell\ChangesToTFS.csv -workItem 189 -dateFrom D2014-10-20T00:00 -dateTo D2014-10-24T00:00

    Hmmm, so I waited about 3 minutes for any response (because I didn't force in $null for $username, and let the value also retain it's now default " " value), and end up with the following errors:

    Error 2

    Terrible right?  No, this is fine by you?  Alright, let's say I even pass in a real $userName value too, so that all my optional parameters have something specified (and hence removing the actual functionality of having them be optional), and now QueryHistory only has valid parameters and doesn't spend tons of time trying to find user " ", which doesn't exist. Now what?

    Well atleast I didn't have to wait 3 minutes before it came out!  I got exactly the same errors though.  So the New-Object command is still poorly written, probably because the [ordered] function is only available in PowerShell v3.0 and above.  Now, I am not going to force any user to have to download and install the latest version of PowerShell on every possible machine the script might be run on just because they want to take advantage of that functionality, when it is possible to run my version on multiple platforms.

    Alright, so be it, let's remove [ordered] from the script so it can run on older versions of PowerShell.  Ran it again, and wow, I finally get some results!  But are the results what I expected?  Nope, just got one row with one item on it, and half the fields undefined.  Why?  Because your version fails to understand the relationship between the ChangeSets collection produced, and the Changes,  ChangeType, Items, and WorkItems collections and objects that relate to them.  Never mind that, the "break" is at the wrong level, so you will only end up with one item from one change collection, not all the changes made as relates to a given work item.

    I'm not even going to begin to try to explain to you why your suggestion that I could simply run the suggested $vCSChangeSet.Changes pipeline command is bad.  All I can tell you is look at the title of this post and then you tell me how all those filters and the properties I want to report on are available that way.

    My point in all this is BEFORE you take it upon yourself next time to "subtly" hint at someone's experience and knowledge level, maybe try to understand what's behind the code, try it yourself, and actually make sure any changes you suggest work!  At least what I've done works, works on various multiple versions of PowerShell, and fulfills the requirements as laid out.

    Anyways, I'm finding that this whole discussion is starting to devolve into something very unproductive and possibly abusive, as clearly, no suggestions to improve performance or add missing features have been added here so far.  I am not interested in re-writing a script made for the benefit of all TFS users (and yes "newbies" count too) to do exactly what is already being done now, especially when what's been laid out is far worse, and especially when I actually have other work to do. 

    Others may have a better approach, and I welcome and encourage anyone to release their tested and working versions, which I don't doubt might be better than what I've come up with here.  For everyone else, I hope you find the script useful, or at least a good start to reporting your own TFS requirements.


    Diane


    • Edited by Diane Sithoo Wednesday, October 29, 2014 11:09 PM
    Wednesday, October 29, 2014 10:29 PM
  • Soory but I can see that you don't understand.  I am showing you modern "post V11" techniques for managing a script.  I am not trying to deliver a completely rewritten scrip and I will no.  Most I see no need to use a function for what you are doing as it can be done in a simple few lines of PowerShell.  You are free to do it however you like.  You asked for feedback in your OP.  Apparently you want to be told all is perfect.

    I will leave you to sor this out.  Youi woill eventually run into the things I am trying to show you.  Remember that I tried.


    ¯\_(ツ)_/¯

    Wednesday, October 29, 2014 11:10 PM
  • On the contrary, I am not saying my script is perfect, far from it, which is why I put it up here for discussion in the first place.  I do want to write better code, and I do want "constructive" criticism.

    Your early suggestions on code clean-up and help messages were very valuable and constructive.  That quickly devolved into criticizing the experience and knowledge levels of the poster and I take offense to statements like the following:

    "Usually when a coder writes all of those tests it shows that they do not really understand what is happening and are getting into the coders nightmare"

    or

    "YOur design is what is in your way.  It iwas driven by a lack of knowledge of how to program and how PowerShell supports the programming effort."

    Your choice of words really hint at the obvious contempt you have for those of us who you believe not to be at your level.  But, when I demonstrate with your own samples and suggestions the flaws in your logic and you come back with "Soory but I can see that you don't understand.", it is clear to me you have nothing positive to contribute and are unwilling to turn that microscope onto yourself.  What astounds me is your suggestion that I ever asked you for a rewritten script or to provide one that ONLY works with the latest greatest.

    Forget that other people might not have access to the latest versions of PowerShell.  Forget that some people here are less experienced than others and might benefit from comments to spell out the work being done.  Forget that some people downloading the script might just want to hurry up and get on with it, with few modifications, and not have to first study a year of PowerShell before they are deigned worthy to even look at it.  Forget that certain assumptions are completely incorrect, leading one to wonder who is really not grasping "basic concepts" (e.g. default values of non-specified parameters are always an empty-string or empty-string is equivalent to null - really?). 

    I have never as a poster or answerer tried to make anyone feel that there was only ever "one right approach", and that when they didn't agree with me one hundred percent, that they obviously were "lacking" or "deficient" in knowledge, experience, or just plain common sense.  The purpose of these forums is to encourage learning, offer constructive advice, and to promote a helpful inclusive community for everyone who dares to ask a question, participate in a discussion, or even want a "quick fix" for an urgent problem and come here for help and support.

    I didn't realize this was a place for only so-called "experts" to talk to other "experts" about theoretical, but non-working pseudocode based on restrictive requirements and suspect assumptions.  My mistake, because I thought most people come here for thought-provoking discussion and answers to urgent questions, not to be told to go do some research, get some experience, or hire a consultant!


    Diane

    Wednesday, October 29, 2014 11:57 PM
  • And I stick by my criticism.  You lack experience and knowledge in scripting and coding.   It is snot intended as negative criticism.  It is intended to make you ask some tough questions. 

    I have been programming for more than 30 years.  I am applying my experience to you request for feedback. Your script says all that is needed about your experience level.

    Don't take it personally.  You still have much to learn.  You will eventually understand the points of criticism.  They will point you in new directions.  The exact ones you choose will be up to you.

    Again - don't take the criticism personally.  If you don't understand what I am trying to show you then just file it for future reference.


    ¯\_(ツ)_/¯

    Thursday, October 30, 2014 12:05 AM
  • Yes, and your script and comments speak for you as well. 

    You have not offered anything constructive, have not made any case for or offered any explanations as to why your broken pseudo-code works in "theory" better or faster than my real working code other than "that's the way to do it".

    You couldn't even properly present how to introduce help messages until I referred you to another post on it, and obviously didn't bother to understand the requirements, because if you had, you never would have made the suggestion in the first place!  Funny how you explicitly wanted me to get rid of the Mandatory=$false clauses in the parameter definitions because they were default and should already be "understood" by those in the know, and yet even with that could still suggest using HelpMessage under those circumstances.  At least I admit it if I've got something wrong!

    You have not addressed any of the issues or concerns I've identified (using your own suggestions and code demonstrated with results) and instead chosen to ignore them in favor of once again accusing me of being ignorant.  If I'm ignorant, please let me know how and why I am with valid real-world business issues that have resulted from my apparently bad code that would have been addressed by using your methodology instead.  Show me the money!

    By the way, years of programming does not necessarily equate to depth of knowledge.  I know several people with similar years of programming who have no real clue about proper design and architecture.  I also know several talented recent graduates who's intelligence and knowledge far exceeds their years.  You know nothing about me so cannot presume to know how long I've been programming for, and how much education and experience I've had.  I won't even dignify that with a response as to how long I've been at it, because it really shows "nerve" when I hear such comments from people who usually can't back up what they are saying.

    Don't worry, I won't take anything personally even though accusing someone of being lacking is rather personal.  I know regardless of how much education and experience I've had, there's always room for growth.  If the person telling me I lack experience and knowledge can't even recognize something as simple as what a default parameter's value really is, or that you can't access parent properties on a child object level automatically, there is really no need for me to take note of anything else!


    Diane

    Thursday, October 30, 2014 1:51 AM
  • Diane - it I snot pseudo code it is current and usable code.

    We use  V1 we were stuck with "add-Member" which is kludgy and unreliable, Since V2 we have used the preferred hash table method to feed th new-Object.  In V3 we added [ordered] which will force the preoprty order to be what we have established.

    I know this is all very strange to many who have limited experience with programming and the Net classes and that you were just copying what you saw on the Inet.  Look at the PosH Team blog for more direction in how to do these newer things.  Since PosH V1 most things have eveolved.

    I am not saying what you did was absolutely wrong.  You asked for a review.  I gave you  a review.  I am sorry that you cannot see how it is useful and I still won't rewrite your code.  I give you the structure and leave iit up to you to understand.

    Oh - and it does work.


    ¯\_(ツ)_/¯

    Thursday, October 30, 2014 1:59 AM
  • Right... which is why I got all those wonderful errors when I ran it verbatim! Not to mention the totally incorrect result set I got back even after making it work by just filling in all the parameters and not touching a line of your code!

    I don't have any such requirement to enforce order, or have to use V3.  Not sure why extraneous requirements need to be added here.  Add-Member may be "kludgy" but works in all versions, and for this particular use-case, doesn't introduce any issues that I've seen.  Yes things have evolved and that's great, but you need to understand that not everyone is able to upgrade their environment.  Again, what's the case for this particular requirement here?  Why must I force users to use a later version of PowerShell when they don't have to?  I guess you don't believe scripts should be generic.

    Please DON'T rewrite my script! I'm afraid your first attempt only broke it and failed to actually satisfy the requirements I had, resulting in missing columns and data. If I wanted something similar and much less convoluted to simply report the change items like you only managed to gather, I would have just used Get-TfsItemHistory() to begin with. I would wonder about any future attempts when you fail to even take the inputs and outputs into consideration to produce the same thing. That stems from not understanding the requirements.


    Diane



    Thursday, October 30, 2014 2:22 AM
  • I can see both sides: jrv is trying to familiarize Diane with more modern coding techniques, and Diane has a script that worked and asked for a critique. Let's keep it civil and remember that the help here is free and we're volunteers.

    -- Bill Stewart [Bill_Stewart]

    Thursday, October 30, 2014 2:34 AM
    Moderator
  • Thank you Bill, well said.

    For my part, I'm going to concentrate on the positive changes jrv has suggested and use the more modern techniques when appropriate.


    Diane

    Thursday, October 30, 2014 3:03 AM
  • I have updated the script, which can be downloaded from the link at the top of the post to reflect the following changes:

    • Enhancements:
      • Add-in ChangeSet properties.
      • Converted comments to messages which can be listed with the "Get-Help" cmdlet.
      • Use newer methodology to assign collections and properties to objects (still compliant with PowerShell v.1)
      • Remove extraneous code (e.g. Extra comparisons, extra Select-Object statement, unnecessary Select Unique parameter, extra initial sort column).
    • Fixes:
      • Report Changeset.Owner instead of Item.ChangedBy in order to more properly reflect who made the change to an item.

    Thank you to jrv for suggesting some of the enhancements.


    Diane




    Thursday, October 30, 2014 8:28 PM
  • I have updated the script again to reflect the following changes:

    • Enhancements:
      • Added changeSet filter parameter.
    • Fixes:
      • Include changesets not associated with any work item in the results, if no work item filter is specified.

    Diane

    Thursday, November 13, 2014 9:20 PM