#Requires -Version 5.0 <# .Synopsis stream_report.ps1 outputs a list of all files in the currently selected stream as a .csv file which is opened in the default application (Excel usually). It is meant for release streams. .Description This is intended as a custom tool for P4V. It can be easily installed for P4V using Install-P4VUtils.ps1 The output format is a CSV file: depot, branch, path, filename, revision, changelist, job, action, description, filetype, datechanged, user, JobStatus, JobDescription, JIRAIssue Assumes 2 level streams depots: //depot/stream_name .Parameter p4port The P4PORT to use .Parameter p4user The P4USER to use .Parameter stream The name of the stream spec to report on. .Parameter freeze date/time Date/time (in p4 format without spaces: yyyyy/mm/dd:hh:mm:ss) .Example As custom tool in P4V: Application: powershell Arguments: -f c:\path\to\stream_report.ps1 $p $u %t $D Where c:\path\to is the name of directory containing the script Check option “Run tool in terminal window” If not debugging, check option “Close window upon completion” #> # Doesn't work in older versions of Powershell # [CmdletBinding()] param ( [string]$p4port, [string]$p4user, [string]$stream) Function Log([string]$message) { # Logs output to file and to console $datetime = Get-Date -format "yyyy\/MM\/dd HH\:mm\:ss" "$datetime $message" | write-host } class P4File { [string]$DepotFile [string]$Rev [string]$Change [string]$Action [string]$Filetype [string]$Desc [string]$Project [string]$Branch [string]$Folder [string]$Filename P4File( [string]$df, [string]$r, [string]$chg, [string]$act, [string]$t ){ $this.DepotFile = $df $this.Rev = $r $this.Change = $chg $this.Action = $act $this.Filetype = $t # Split //depot/stream/folder1/folder2/file $s = $this.DepotFile.split("/") $this.Project = $s[2] $this.Branch = $s[3] $this.Filename = $s[-1] $this.Folder = "" if ($s.length -gt 5) { $this.Folder = $s[4..($s.length - 2)] -join "/" } } } # Note that using classes (Powershell 5+) offers sensible variable scoping and function/method returns! class P4Stream { [string]$Name P4Stream([string]$n){ $this.Name = $n } [string[]] GetPaths() { $myargs = $global:p4baseargs + "stream", "-o", $this.Name Log "p4 $myargs" $stream_spec = & p4 @myargs $paths = @() $inPath = $false foreach ($line in $stream_spec) { if ($line -match "^#.*") { continue } if ($line -match "^Paths:") { $inPath = $true } if ($inPath) { if ($line -match "^$") { $inPath = $false } else { $paths += $line } } } Log "Paths:" Log $paths -join "`r`n" Log "end" $imports = $paths | select-string "^\s+import" Log "Imports: $imports" $import_map = [ordered]@{} foreach ($line in $imports) { # Allow for quoted paths (which contain spaces) Log "PArsing: $line" switch -regex ($line) { "^\s+import\s+(\S+)\s+(\S+)$" { Log "m: $($matches[1]) - $($matches[2])" $import_map[$matches[1]] = $matches[2] } "^\s+import\s+`"(.+?)`"\s+(\S+)$" { Log "m: $($matches[1]) - $($matches[2])" $import_map[$matches[1]] = $matches[2] } "^\s+import\s+(\S+)\s+`"(.+?)`"$" { Log "m: $($matches[1]) - $($matches[2])" $import_map[$matches[1]] = $matches[2] } "^\s+import\s+`"(.+?)`"\s+`"(.+?)`"$" { Log "m: $($matches[1]) - $($matches[2])" $import_map[$matches[1]] = $matches[2] } } } $import_sources = @("$($this.Name)/...") $import_map.keys | ForEach-Object{ Log "$_ : $($import_map[$_])" $import_sources += $import_map[$_] } return $import_sources } } class P4Change { [string]$Change [string]$Date [string]$Client [string]$User [string]$Status [string]$Description P4Change([string]$chg){ $this.Change = $chg Log "Creating change: $chg" $p4args = $global:p4baseargs + "change", "-o", $this.Change Log "p4 $p4args" $change_spec = & p4 @p4args $inDesc = $false foreach ($line in $change_spec) { switch -regex ($line) { "^Change:\s+(\S+)$" { $this.Change = $matches[1] } "^Date:\s+(.*)$" { $this.Date = $matches[1] } "^Client:\s+(\S+)$" { $this.Client = $matches[1] } "^User:\s+(\S+)$" { $this.User = $matches[1] } "^Status:\s+(\S+)$" { $this.Status = $matches[1] } "^Description:" { $inDesc = $true continue } } if ($inDesc) { if ($line -match "^$") { $inDesc = $false } else { if ($line -notmatch "^Description:" ) { $line = $line -Replace "\t", "" $line = $line -Replace """", """""" $this.Description += $line } } } } } } try { if ($p4port -eq $null -or $p4port -eq "") { throw "ERROR: please specify parameter p4port" } if ($p4user -eq $null -or $p4user -eq "") { throw "ERROR: please specify parameter p4user" } if ($stream -eq $null -or $path -eq "") { throw "ERROR: please specify parameter stream" } Log "Args p4port:'$p4port' p4user:'$p4user' stream:'$stream'" $p4baseargs = "-p", $p4port if ($p4user -ne "default") { $p4baseargs += "-u", $p4user } $changes = @{} $changenums = @{} $all_files = @() $sobj = [P4Stream]::new($stream) $file_paths = $sobj.GetPaths() Log "Files paths: $file_paths" foreach ($path in $file_paths) { Log "Getting files in $path" $p4args = $p4baseargs + "-F", "%depotFile%|%depotRev%|%change%|%action%|%type%", "files", "$path" Log "p4 $p4args" $files = & p4 @p4args foreach ($line in $files) { Log "Processing $line" $depotfile, $depotrev, $changeno, $action, $type = $line.split("|") $depotrev = $depotrev -Replace "#", "" $changeno = $changeno -Replace "change ", "" [P4File]$f = [P4File]::new($depotfile, $depotrev, $changeno, $action, $type) $all_files += $f $changenums[$changeno] = 1 } } # Populate change descs Log "Processing changes" $changenums.keys | ForEach-Object{ Log "Processing $_" $changes[$_] = [P4Change]::new($_) } $tmpfile = New-Temporaryfile $outputfile = $tmpfile.FullName -replace ".tmp", "" $outputfile += ".csv" # Write CSV header "depot,branch,path,filename,revision,changelist,action,filetype,datechanged,user,Description,Job,JobStatus,JobDescription,JIRAIssue" | add-content -path $outputfile foreach ($f in $all_files) { $chg = $changes[$f.Change] "$($f.Project),$($f.Branch),$($f.Folder),$($f.Filename),$($f.Rev),$($f.Change),$($f.Action),$($f.Filetype),$($chg.Date),$($chg.user),""$($chg.Description)"",,," | add-content -path $outputfile } start "$outputfile" exit 0 } Catch { write-error $_.Exception exit 1 }