I want to use PowerShell to update my application's Assembly info like Product Name, File Description in TFS vNext build. I want to use build variables in build definition and pass it to the script.
I have done the same to update version number with the help of this(https://writeabout.net/2015/11/01/set-assembly-and-app-version-to-a-matching-build-name-in-tfs-2015-or-vso-build-vnext/).
I need help with the script to replace "Product Name" or "Description" value with new value.
You can replace the name & description with a small script:
Param(
[string]$filePath,
[string]$newProductName,
[string]$newProductDesc
)
$patternProductName = '\[assembly: AssemblyProduct\("(.*)"\)\]'
$patternProductDesc = '\[assembly: AssemblyDescription\("(.*)"\)\]'
(Get-Content $filePath) | ForEach-Object{
if($_ -match $patternProductName){
'[assembly: AssemblyProduct("{0}")]' -f $newProductName
}
elseif($_ -match $patternProductDesc){
'[assembly: AssemblyDescription("{0}")]' -f $newProductDesc
}
else{
$_
}
} | Set-Content $filePath
Now during the build call this script and in the arguments send the 3 args: AssmeblyInfo file path, your new product name and your new product description.
Related
Using PowerShell script, I am able to read the Team Foundation Server workspace version numbers.
Now I would like to update the .NET Core Project's Assembly/or/File version in its properties...
I am looking for a PowerShell command that updates properties of the project .. is that even possible?
Or a command that would update one of the properties in the appsettings.json file's data
Ex:
"AssemblyVersion": "12345"
Thank you for taking the time to look at this if you are looking to help me
...and Sorry if you are looking for a solution and there is none yet
Perry
p.s.
What I am trying to achieve is the equivalent of Tortoise's "subwcrev" command to update the "AssemblyInfoTemplate"
You can achieve this by using the .NET framework's System.Reflection.AssemblyInfo class, which provides a way to modify the AssemblyInfo file in a project.
e.g
$assemblyInfoFile = 'path\to\AssemblyInfo.cs'
$assemblyVersion = '1.0.0.0'
$fileVersion = '1.0.0.0'
$assemblyInfoContent = Get-Content $assemblyInfoFile
$assemblyInfoContent = $assemblyInfoContent -replace 'AssemblyVersion\("[^"]*"\)', "AssemblyVersion(`"$assemblyVersion`")"
$assemblyInfoContent = $assemblyInfoContent -replace 'AssemblyFileVersion\("[^"]*"\)', "AssemblyFileVersion(`"$fileVersion`")"
Set-Content $assemblyInfoFile $assemblyInfoContent
Once you have updated the AssemblyInfo file, you should be able to see the changes reflected in the project properties in Visual Studio or any other development environment you are using.
Yé!
I've done it.
And I would like to add that I got most of my info from this site ;)
See the code attached c/w comments
### TFS HISTORY
Set-Alias tfs "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\TF.exe"
######### some commands I tried that I want to keep
### tfs history . /r /noprompt /stopafter:1 /version:W | Out-File -FilePath C:\temp\OUTPUT.txt
### Get-Content "C:\temp\OUTPUT.txt" -tail 1 | Out-File -FilePath "C:\temp\output.csv"
### $variable = Get-Content "C:\temp\OUTPUT.txt" -tail 1 ### gets the last line
#==========================================================================
$variable = tfs history . /r /noprompt /stopafter:1 /version:W # get the version of tfs
########## the three lines below are the result of the "tfs history" command
# Changeset User Date Comment
# --------- ----------------- ---------- --------------
# 97504 Dorion, Perry 2023-02-09 v#
# what I need is the "97504" number
$vlinearray = $variable.split("`r`n") # split into lines
$vspacearray = $vlinearray[$vlinearray.Length -1].split(" ") # split the last line into space separated strings
######### for debugging purposes
# Write-Output $vspacearray[0] #get the first string of the last line
###################################################
############ SAVES to appsettings.json ############
### not being used
# $pathToJson = "C:\Users\PDorion\Documents\DevNETCore\PNR\PEA\appsettings.json"
# $a = Get-Content $pathToJson | ConvertFrom-Json
# $a.AssemblyVersion = $vspacearray[0]
# $a | ConvertTo-Json | set-content $pathToJson
### I needed a way to find the path of the ruinning script; mine differs from my co-workers who work on the same project
### gets path of this script i.e. "C:\Users\PDorion\Documents\DevNETCore\PNR\PEA\"
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition;
$scriptName = "\PEA.csproj";
### get the value from TFS HISTORY
### DOTIFY the 5 digit number ==> #.#.#.## format
### our TFS version numbers are too big and do not jive well with Assembly number type
$splitval = $vspacearray[0].ToCharArray()
$Result = ""
$dot = ""
$dotcnt = 0
foreach ($num in $splitval) {
$Result = "$Result$dot$num"
$dotcnt++
if ($dotcnt -lt 4 ) {
$dot = "."
}else{
$dot = ""
}
}
### Write-Output "Result $Result"
$projFile = "$scriptPath$scriptName";
### get Project xml content i.e. the properties
$config = (Get-Content $projFile) -as [Xml];
$ns = New-Object System.Xml.XmlNamespaceManager($config.NameTable);
$ns.AddNamespace("cs", $config.DocumentElement.NamespaceURI);
### the following is ugly but it works
$config.DocumentElement.SelectNodes("//cs:AssemblyVersion", $ns) | % {
$node = $_;
$node.InnerText = $Result;
}
$config.DocumentElement.SelectNodes("//cs:FileVersion", $ns) | % {
$node = $_;
$node.InnerText = $Result;
}
### save the sucker
$config.Save($projFile);
I'm trying to convert a few thousand home videos to a smaller format. However, encoding the video changed the created and modified timestamp to today's date. I wrote a powershell script that successfully (somehow) worked by writing the original file's modified timestamp to the new file.
However, I couldn't find a way in powershell to modify the "Media created" timestamp in the file's details properties. Is there a way to add a routine that would either copy all of the metadata from the original file, or at least set the "media created" field to the modified date?
When I searched for file attributes, it looks like the only options are archive, hidden, etc. Attached is the powershell script that I made (please don't laugh too hard, haha). Thank you
$filepath1 = 'E:\ConvertedMedia\Ingest\' # directory with incorrect modified & create date
$filepath2 = "F:\Backup Photos 2020 and DATA\Data\Photos\Photos 2021\2021 Part1\Panasonic 3-2-21\A016\PRIVATE\PANA_GRP\001RAQAM\" # directory with correct date and same file name (except extension)
$destinationCodec = "*.mp4" # Keep * in front of extension
$sourceCodec = ".mov"
Get-ChildItem $filepath1 -File $destinationCodec | Foreach-Object { # change *.mp4 to the extension of the newly encoded files with the wrong date
$fileName = $_.Name # sets fileName variable (with extension)
$fileName # Optional used during testing- sends the file name to the console
$fileNameB = $_.BaseName # sets fileNameB variable to the filename without extension
$filename2 = "$filepath2" + "$fileNameB" + "$sourceCodec" # assembles filepath for source
$correctTime = (Get-Item $filename2).lastwritetime # used for testing - just shows the correct time in the output, can comment out
$correctTime # prints the correct time
$_.lastwritetime = (Get-Item $filename2).lastwritetime # modifies lastwritetime of filepath1 to match filepath2
$_.creationTime = (Get-Item $filename2).lastwritetime # modifies creation times to match lastwritetime (comment out if you need creation time to be the same)
}
Update:
I think I need to use Shell.Application, but I'm getting an error message "duplicate keys ' ' are not allowed in hash literals" and am not sure how to incorporate it into the original script.
I only need the "date modified" attribute to be the same as "lastwritetime." The other fields were added just for testing. I appreciate your help!
$tags = "people; snow; weather"
$cameraModel = "AG-CX10"
$cameraMaker = "Panasonic"
$mediaCreated = "2/16/1999 5:01 PM"
$com = (New-Object -ComObject Shell.Application).NameSpace('C:\Users\philip\Videos') #Not sure how to specify file type
$com.Items() | ForEach-Object {
New-Object -TypeName PSCustomObject -Property #{
Name = $com.GetDetailsOf($_,0) # lists current extended properties
Tags = $com.GetDetailsOf($_,18)
CameraModel = $com.GetDetailsOf($_,30)
CameraMaker = $com.GetDetailsOf($_,32)
MediaCreated = $com.GetDetailsOf($_,208)
$com.GetDetailsOf($_,18) = $tags # sets extended properties
$com.GetDetailsOf($_,30) = $cameraModel
$com.GetDetailsOf($_,32) = $cameraMaker
$com.GetDetailsOf($_,32) = $mediaCreated
}
}
Script Example
File Properties Window
I think your best option is to drive an external tool/library from Powershell rather than using the shell (not sure you can actually set values this way tbh).
Its definitely possible to use FFMpeg to set the Media Created metadata of a file like this:
ffmpeg -i input.MOV -metadata creation_time=2000-01-01T00:00:00.0000000+00:00 -codec copy output.MOV
This would copy input.MOV file to new file output.MOV and set the Media Created metadata on the new output.MOV. This is very inefficient - but it does work.
You can script ffmpeg something like the below. The script will currently output the FFMpeg commands to the screen, the commented out Start-Process line can be used to execute ffmpeg.
gci | where Extension -eq ".mov" | foreach {
$InputFilename = $_.FullName;
$OutputFilename = "$($InputFilename)-fixed.mov";
Write-Host "Reading $($_.Name). Created: $($_.CreationTime). Modifed: $($_.LastWriteTime)";
$timestamp = Get-Date -Date $_.CreationTime -Format O
Write-Host "ffmpeg -i $InputFilename -metadata creation_time=$timestamp -codec copy $OutputFilename"
# Start-Process -Wait -FilePath C:\ffmpeg\bin\ffmpeg.exe -ArgumentList #("-i $InputFilename -metadata creation_time=$timestamp -codec copy $($OutputFilename)")
}
In TFS 2015 new build system, did the functionality to automatically add build number to Global List (Build - Project Name) upon build complete removed?
Do I need to write a custom PowerShell task to accomplish this?
Note: XAML builds still add build number to Global List as it did before.
Since many features are still missing in the vNext build system, I've made a PowerShell script that do the Job.
In a near futur, I plan to update this script to support IntegratedIn field filling and to convert the script as a custom build task.
[CmdletBinding(SupportsShouldProcess=$false)]
param()
function Update-GlobalListXml
{
[CmdletBinding(SupportsShouldProcess=$false)]
param(
[xml]$globalListsDoc,
[parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$glName,
[parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$buildNumber
)
Write-Verbose "Checking whether '$glName' exists"
$buildList = $globalListsDoc.GLOBALLISTS.GLOBALLIST | Where-Object { $_.name -eq $glName }
if ($buildList -eq $null)
{
Write-Host "GlobalList '$glName' does not exist and will be created"
$globalLists = $globalListsDoc.GLOBALLISTS
if($globalLists.OuterXml -eq $null)
{
$newDoc = [xml]"<gl:GLOBALLISTS xmlns:gl="""http://schemas.microsoft.com/VisualStudio/2005/workitemtracking/globallists"""></gl:GLOBALLISTS>"
$globalLists = $newDoc.GLOBALLISTS
}
$globalList = $globalLists.OwnerDocument.CreateElement("GLOBALLIST")
$globalList.SetAttribute("name", $glName)
$buildList = $globalLists.AppendChild($globalList)
}
if(($buildList.LISTITEM | where-object { $_.value -eq $buildNumber }) -ne $null)
{
throw "The LISTITEM value: '$buildNumber' already exists in the GLOBALLIST: '$glName'"
}
Write-Host "Adding '$buildNumber' as a new LISTITEM in '$glName'"
$build = $buildList.OwnerDocument.CreateElement("LISTITEM")
$build.SetAttribute("value", $buildNumber)
$buildList.AppendChild($build) | out-null
return $buildList.OwnerDocument
}
function Invoke-GlobalListAPI()
{
[CmdletBinding(SupportsShouldProcess=$false)]
param(
[parameter(Mandatory=$true)][Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore]$wiStore,
[parameter(Mandatory=$true,ParameterSetName="Import")][switch]$import,
[parameter(Mandatory=$true,ParameterSetName="Import")][xml]$globalLists,
[parameter(ParameterSetName="Export")][switch]$export
)
try {
if($import)
{
$wiStore.ImportGlobalLists($globalLists.OuterXml) # Account must be explicitly in the Project Administrator Group
}
if($export)
{
return [xml]$wiStore.ExportGlobalLists()
}
}
catch [Microsoft.TeamFoundation.TeamFoundationServerException] {
Write-Error "An error has occured while exporting or importing GlobalList"
throw $_
}
}
function Get-WorkItemStore()
{
[CmdletBinding(SupportsShouldProcess=$false)]
param(
[parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$tpcUri,
[parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$agentWorker
)
# Loads client API binaries from agent folder
$clientDll = Join-Path $agentWorker "Microsoft.TeamFoundation.Client.dll"
$wiTDll = Join-Path $agentWorker "Microsoft.TeamFoundation.WorkItemTracking.Client.dll"
[System.Reflection.Assembly]::LoadFrom($clientDll) | Write-Verbose
[System.Reflection.Assembly]::LoadFrom($wiTDll) | Write-Verbose
try {
Write-Host "Connecting to $tpcUri"
$tfsTpc = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tpcUri)
return $tfsTpc.GetService([Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore])
}
catch [Microsoft.TeamFoundation.TeamFoundationServerException] {
Write-Error "An error has occured while retrieving WorkItemStore"
throw $_
}
}
function Get-WITDataStore64
{
[CmdletBinding(SupportsShouldProcess=$false)]
param()
if($env:VS140COMNTOOLS -eq $null)
{
throw New-Object System.InvalidOperationException "Visual Studio 2015 must be installed on the build agent" # TODO: Change it by checking agent capabilities
}
$idePath = Join-Path (Split-Path -Parent $env:VS140COMNTOOLS) "IDE"
return Get-ChildItem -Recurse -Path $idePath -Filter "Microsoft.WITDataStore64.dll" | Select-Object -First 1 -ExpandProperty FullName
}
function Update-GlobalList
{
[CmdletBinding(SupportsShouldProcess=$false)]
param()
# Get environment variables
$tpcUri = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI
Write-Verbose "Team Project Collection Url: '$tpcUri'"
$teamProjectName = $env:SYSTEM_TEAMPROJECT
Write-Verbose "Team Project: '$teamProjectName'"
$buildNumber = $env:BUILD_BUILDNUMBER
Write-Verbose "Build Number: '$buildNumber'"
$agentHome = $env:AGENT_HOMEDIRECTORY
Write-Verbose "Agent home direrctory: '$agentHome'"
$globalListName = "Builds - $teamProjectName"
Write-Verbose "GlobalList name: '$teamProjectName'"
# Copy 'Microsoft.WITDataStore64.dll' from Visual Studio directory to AgentBin directory if it does not exist
$agentWorker = Join-Path $agentHome "agent\Worker"
$targetPath = Join-Path $agentWorker "Microsoft.WITDataStore64.dll" # Only compatible with x64 process #TODO use constant instead
if(-not (Test-Path $targetPath))
{
$wITDataStore64FilePath = Get-WITDataStore64
Write-Host "Copying $wITDataStore64FilePath to $targetPath"
Copy-Item $wITDataStore64FilePath $targetPath | Write-Verbose
}
$wiStore = Get-WorkItemStore -tpcUri $tpcUri -agentWorker $agentWorker
# Retrive GLOBALLISTS
$xmlDoc = Invoke-GlobalListAPI -export -wiStore $wiStore
$gls2 = Update-GlobalListXml -globalListsDoc $xmlDoc -glName $globalListName -buildNumber $buildNumber
Invoke-GlobalListAPI -import -globalLists $gls2 -wiStore $wiStore
}
Update-GlobalList
Here is the link of the Github repo, feedbacks are welcome => https://github.com/GregoryOtt/UpdateWiBuildNum/blob/master/Update-GlobalList.ps1
[disclaimer - I work on the new build system]
That global list on the workitem is a mechanism that dated back to the original release of TFS. It's one that sort of worked in that day and age (days of nightly builds, pre-CI and CD agility). It's starts to fall apart and doesn't show as proper relationships in TFS. I worked on WIT at that time and we needed a queryable mechanism and that's what we had (blame me :)
So, when we started a new build system, we didn't want to rebuild things and repeat the same mistakes. We're trying to take an agile, incremental approach to a better build system.
In the next sprint (88), we are starting work on proper links between builds and workitems and the WIT team is also doing work to make them more first class. The first thing you'll see is a link on the WIT form and that should hopefully make QU1 as well (at least parts of it).
We realize this does leave a few gaps but we are working to close them (gated and label sources being two others) and hopefully in a better way for a better long term product.
As far as a workaround goes, it should be possible to automate via powershell and our clients but we don't have anything canned for others to use.
I'm brand new to powershell, and I'm finding a very odd behavior when I try and pass a parameter into a function:
Code
Param(
[Parameter(Mandatory=$true)][string]$vhdSourceDir,
[Parameter(Mandatory=$true)][string]$vhdDestinationDir,
[Parameter(Mandatory=$true)][array]$hypervisorList,
[int]$vhdKeep = 10
)
function getDirectoryBuildNumber ($dir) {
# Returns a number like 627c6ddeb8776914 from a path like:
# c:\BulidAgent\work\627c6ddeb8776914\packer\windows\box\hyperv\win2012r2std-cheflatest-001.box
return ( Get-ChildItem $dir | Where-Object {$_.Name -match "^[A-Za-z0-9]{16}$" } | Sort-Object LastAccessTime -Descending | Select-Object -First 1 )
}
function findBoxFile ($dir, $build) {
echo "looking for build $build at $dir "
echo "the dir is $dir and the build is $build"
# e.g c:\BulidAgent\work\627c6ddeb8776914\packer\windows\box\hyperv\win2012r2std-cheflatest-001.box
return ( Get-ChildItem $dir\$build\packer\windows\box\hyperv\ | Where-Object Extension -in '.box' | Sort-Object LastAccessTime -Descending | Select-Object -First 1 )
}
Function Main ()
{
$THEBUILD=getDirectoryBuildNumber($vhdSourceDir)
echo "THEBUILD is $THEBUILD"
findBoxFile($vhdSourceDir,$THEBUILD)
#echo "BOXFILE is $BOXFILE"
}
main
Problem
Here are the parameters that I call the script with:
.\boxMove.ps1 -vhdSourceDir C:\BuildAgent\work -vhdDestinationDir e:\ -hypervisorList 'foobar'
Here is the output it generates
THEBUILD is 527c6ddeb8776914
looking for build at C:\BuildAgent\work 527c6ddeb8776914
the dir is C:\BuildAgent\work 527c6ddeb8776914 and the build is
Get-ChildItem : Cannot find path 'C:\BuildAgent\work 527c6ddeb8776914\packer\windows\box\hyperv\' because it does not exist.
The parameters show up out of order. For example, the phrase 'looking for build should appear like so"
looking for build 527c6ddeb8776914 at C:\BuildAgent\work
but it shows up as
looking for build at C:\BuildAgent\work 527c6ddeb8776914
Also the phrase 'the dir is..' should read
the dir is C:\BuildAgent\work and the build is 527c6ddeb8776914
but it reads as
the dir is C:\BuildAgent\work 527c6ddeb8776914 and the build is
Why is powershell not printing the strings in order?
When you pass parameters to functions do not encapsulate them in parentheses and where multiple parameters are being passed, they should not be comma separated, instead a space should be used to separate them.
For instance:
findBoxFile($vhdSourceDir,$THEBUILD)
should read:
findBoxFile $vhdSourceDir $THEBUILD
Then you'll find that this removes the issue you were encountering with the incorrectly ordered output.
Firstly, clean up your function definitions. Instead of
function (params){code}
you should use
function
{
Param ($Param1,$param2)
code
}
Also, when accepting an array as a param, instead of using [array], specify the array type, (probably string in your case), so that would be (line 4)
[Parameter(Mandatory=$true)][string[]]$hypervisorList,
It's hard do say exactly why the script fails, but I'd start by cleaning up the obvious issues and test again.
Does anyone have a link or script that uses PowerShell to inventory the Scheduled Tasks on a server, including the Action?
I am able to get the Scheduled Service com object and what I would call "top level" properties (name, state, lastruntime), but would like to also get information from the "Actions" part of the Schedule Tasks (essentially, the name of Scheduled Task and its commandline).
For example:
$schedule = new-object -com("Schedule.Service")
$schedule.connect()
$tasks = $schedule.getfolder("\").gettasks(0)
$tasks | select Name, LastRunTime
foreach ($t in $tasks)
{
foreach ($a in $t.Actions)
{
$a.Path
}
}
The above snippet of code works in terms of listing the tasks; but the loop on the Actions simply does not seem to do anything, no error, no output whatsoever.
Any help would be appreciated.
This is probably very similar to current answers, but I wrote a quick script to get you going. The problem with your current script is that there is no Actions property in a task. You need to extract it from the xml task-definition that the comobject provides. The following script will return an array of objects, one per scheduled task. It includes the action if the action is to run one or more command. It's just to get you going, so you need to modify it to include more of the properties if you need them.
function getTasks($path) {
$out = #()
# Get root tasks
$schedule.GetFolder($path).GetTasks(0) | % {
$xml = [xml]$_.xml
$out += New-Object psobject -Property #{
"Name" = $_.Name
"Path" = $_.Path
"LastRunTime" = $_.LastRunTime
"NextRunTime" = $_.NextRunTime
"Actions" = ($xml.Task.Actions.Exec | % { "$($_.Command) $($_.Arguments)" }) -join "`n"
}
}
# Get tasks from subfolders
$schedule.GetFolder($path).GetFolders(0) | % {
$out += getTasks($_.Path)
}
#Output
$out
}
$tasks = #()
$schedule = New-Object -ComObject "Schedule.Service"
$schedule.Connect()
# Start inventory
$tasks += getTasks("\")
# Close com
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($schedule) | Out-Null
Remove-Variable schedule
# Output all tasks
$tasks
Ex. of output
PS > .\Untitled1.ps1 | ? { $_.Name -eq "test" }
Actions : notepad.exe c:\test.txt
calc.exe
Path : \test
Name : test
LastRunTime : 30.12.1899 00:00:00
NextRunTime : 17.03.2013 13:36:38
Get the PowerShellPack from the W7 RK, and try get-scheduledtask
http://archive.msdn.microsoft.com/PowerShellPack
Excerpt From MSDN:
The Windows 7 Resource Kit PowerShell Pack contains 10 modules to do all sorts of interesting things with PowerShell. Import-Module PowerShellPack actually imports 10 modules for you to use. Here’s a brief overview of each of the modules.
WPK Create rich user interfaces quick and easily from Windows PowerShell. Think HTA, but easy. Over 600 scripts to help you build quick user interfaces
TaskScheduler List scheduled tasks, create or delete tasks
FileSystem Monitor files and folders, check for duplicate files, and check disk space
IsePack Supercharge your scripting in the Integrated Scripting Environment with over 35 shortcuts
DotNet Explore loaded types, find commands that can work with a type, and explore how you can use PowerShell, DotNet and COM together
PSImageTools Convert, rotate, scale, and crop images and get image metadata
PSRSS Harness the FeedStore from PowerShell
PSSystemTools Get Operating System or Hardware Information
PSUserTools Get the users on a system, check for elevation, and start-processaadministrator
PSCodeGen Generates PowerShell scripts, C# code, and P/Invoke
Another way would be a script I wrote called Get-ScheduledTask.ps1, available in this article:
How-To: Use PowerShell to Report on Scheduled Tasks
In this way you only need this single script and you don't need to download or install anything else.
Bill
I know I'm late to the party, but the answer provided by #Frode F., while it works, is technically not correct.
You can access items of the Actions collection of a scheduled task via PowerShell, it's just not immediately obvious. I had to figure this out myself today as well.
Here's the code to do this all in PowerShell, without having to muck around with XML:
# I'm assuming that you have a scheduled task object in the variable $task:
$taskAction = $task.Definition.Actions.Item.Invoke(1) # Collections are 1-based
That's all there is to getting a single item out of the collection without using foreach.
Because the Actions property is a collection which contains a parameterized property Item (e.g. in C# you would write myTask.Actions[0] or in VB myTask.Actions.Item(1)), PowerShell represents the Item property as a PSParameterizedProperty object. To call the methods associated with the property, you use the Invoke method (for the getter) and InvokeSet method (for the setter).
I ran a quick test running the OP's code and it worked for me (I'm running PowerShell 4.0, however, so maybe that has something to do with it):
$schedule = new-object -com("Schedule.Service")
$schedule.connect()
$tasks = $schedule.getfolder("\").gettasks(0)
$tasks | select Name, LastRunTime
foreach ($t in $tasks)
{
foreach ($a in $t.Actions)
{
Write-Host "Task Action Path: $($a.Path)" # This worked
Write-Host "Task Action Working Dir: $($a.workingDirectory)" # This also worked
}
$firstAction = $t.Actions.Item.Invoke(1)
Write-Host "1st Action Path: $($firstAction.Path)"
Write-Host "1st Action Working Dir: $($firstAction.WorkingDirectory)"
}
HTH.
here a quick one based on: https://blogs.technet.microsoft.com/heyscriptingguy/2015/01/17/weekend-scripter-use-powershell-to-document-scheduled-tasks/
Uses Powershell: Get-ScheduledTask and Get-ScheduledTaskInfo
### run like >> Invoke-Command -ComputerName localhost, server1, server2 -FilePath C:\tmp\Get_WinTasks.ps1
$taskPath = "\"
$outcsv = "c:\$env:COMPUTERNAME-WinSchTaskDef.csv"
Get-ScheduledTask -TaskPath $taskPath |
ForEach-Object { [pscustomobject]#{
Server = $env:COMPUTERNAME
Name = $_.TaskName
Path = $_.TaskPath
Description = $_.Description
Author = $_.Author
RunAsUser = $_.Principal.userid
LastRunTime = $(($_ | Get-ScheduledTaskInfo).LastRunTime)
LastResult = $(($_ | Get-ScheduledTaskInfo).LastTaskResult)
NextRun = $(($_ | Get-ScheduledTaskInfo).NextRunTime)
Status = $_.State
Command = $_.Actions.execute
Arguments = $_.Actions.Arguments }} |
Export-Csv -Path $outcsv -NoTypeInformation