How do I call methods within workflow? - powershell

How do I call methods within workflow?
I am trying to call "Task" from within a workflow and it seems to be getting ignored? I have provided a watered down nonsense code to illustrate what I'm trying to do. basically call a method within the class in parallel and return the results.
Sample Code
Class Something {
[string]Task($item) {
Start-sleep -Seconds 10
Return "Result"
}
[System.Array]GetSomething($list) {
workflow GetWF {
param($listarr)
ForEach -parallel ($item in $listarr) {
$res = InlineScript {
Write-Host("Starting.." + $using:item)
$this.Task($using:item)
}
}
$res
}
Return GetWF -listarr $list
}
}
$list = #('host1','host2','host3','host4')
$Something = [Something]::New()
$Something.GetSomething($list)
Output :
Starting..host1
Starting..host4
Starting..host2
Starting..host3
Desired Result from Example
The issue is how do I get my results back as an array ? In this example above I would like to see the final result to be this:
$Result = #("Result","Result","Result","Result")

Related

[Class]::New() causing blank line on the console

I am working on implementing a singleton class to store some regularly accessed status information for my script, including hacking around the issue of $myInvocation only being populated in the main script. All working as planned with this.
class pxStatus {
static [pxStatus] $singleton = $null
[string]$Context = 'machine'
[string]$Path = $null
[datetime]$StartTime = (Get-Date)
pxStatus ([string]$path) {
if ([pxStatus]::singleton -eq $null) {
$this.Path = $path
[pxStatus]::singleton = $this
} else {
Throw "Singleton already initialized"
}
}
static [pxStatus] Get() {
if ([pxStatus]::singleton -eq $null) {
Throw "Singleton not yet initialized"
} else {
return [pxStatus]::singleton
}
}
}
CLS
[void]([pxStatus]::New((Split-Path ($myInvocation.myCommand.path) -parent)))
([pxStatus]::Get()).StartTime
([pxStatus]::Get()).Context
([pxStatus]::Get()).Path
With one exception. Even with that [void] on the [pxStatus]::New() line, I am getting a blank line in the console. Even $null = ([pxStatus]::New((Split-Path ($myInvocation.myCommand.path) -parent))) is echoing a blank line to the console. And for the life of me I can't see what is causing it.
It's not new that causes a blank line but ([pxStatus]::Get()).StartTime.
To fix the issue, you may output it as string, i.e. not formatted, e.g. ([pxStatus]::Get()).StartTime.ToString()
You problem has already been diagnosed, but I wanted to take a second to show how to actually implement a singleton-like type in PowerShell (see inline comments):
class pxStatus {
# hide backing field from user
hidden static [pxStatus] $singleton = $null
[string]$Context = 'machine'
[string]$Path = $null
[datetime]$StartTime = (Get-Date)
# hide instance constructor, no one should call this directly
hidden pxStatus ([string]$path) {
# Only allow to run if singleton instance doesn't exist already
if ($null -eq [pxStatus]::singleton) {
$this.Path = $path
} else {
Throw "Singleton already initialized - use [pxStatus]::Get()"
}
}
# Use a static constructor to initialize singleton
# guaranteed to only run once, before [pxStatus]::Get() or [pxStatus]::singleton
static pxStatus () {
# grab the path from context, don't rely on user input
if(-not $PSScriptRoot){
throw "[pxStatus] can only be used in scripts!"
}
# this will only succeed once anyway
[pxStatus]::singleton = [pxStatus]::new($PSScriptRoot)
}
static [pxStatus] Get() {
# No need to (double-)check ::singleton, static ctor will have run already
return [pxStatus]::singleton
}
}
[pxStatus]::Get().StartTime

Class based streamReader & xmlReader locks files, function doesn't

I am refactoring some function based XML reader code to class methods, and seeing some issues. With the function, I can run a test and verify the XML loaded right, then change the XML and test for error conditions. But this class based approach fails due to "the file is open in another program", forcing me to close the console before I can revise the XML.
Initially I was using the path directly in the xmlReader. So I moved to a StreamReader input to the xmlReader. And I even played with creating an all new xmlDocument and importing the root node of the loaded XML into that new xmlDocument. None works.
I suspect the reason the function based version works is because the xmlReader variable is local scope, so it goes out of scope when the function completes. But I'm grasping at straws there. I also read that Garbage Collection could be an issue, so I added [system.gc]::Collect() right after the Dispose and still no change.
class ImportXML {
# Properties
[int]$status = 0
[xml.xmlDocument]$xml = ([xml.xmlDocument]::New())
[collections.arrayList]$message = ([collections.arrayList]::New())
# Methods
[xml.xmlDocument] ImportFile([string]$path) {
$importError = $false
$importFile = ([xml.xmlDocument]::New())
$xmlReaderSettings = [xml.xmlReaderSettings]::New()
$xmlReaderSettings.ignoreComments = $true
$xmlReaderSettings.closeInput = $true
$xmlReaderSettings.prohibitDtd = $false
try {
$streamReader = [io.streamReader]::New($path)
$xmlreader = [xml.xmlreader]::Create($streamReader, $xmlReaderSettings)
[void]$importFile.Load($xmlreader)
$xmlreader.Dispose
$streamReader.Dispose
} catch {
$exceptionName = $_.exception.GetType().name
$exceptionMessage = $_.exception.message
switch ($exceptionName) {
Default {
[void]$this.message.Add("E_$($exceptionName): $exceptionMessage")
$importError = $true
}
}
}
if ($importError) {
$importFile = $null
}
return $importFile
}
}
class SettingsXML : ImportXML {
# Constructor
SettingsXML([string]$path){
if ($this.xml = $this.ImportFile($path)) {
Write-Host "$path!"
} else {
Write-Host "$($this.message)"
}
}
}
$settingsPath = '\\Mac\iCloud Drive\Px Tools\Dev 4.0\Settings.xml'
$settings = [SettingsXML]::New($settingsPath)
EDIT:
I also tried a FileStream rather than a StreamReader, with FileShare of ReadWrite, like so
$fileMode = [System.IO.FileMode]::Open
$fileAccess = [System.IO.FileAccess]::Read
$fileShare = [System.IO.FileShare]::ReadWrite
$fileStream = New-Object -TypeName System.IO.FileStream $path, $fileMode, $fileAccess, $fileShare
Still no luck.
I think you're on the right lines with Dispose, but you're not actually invoking the method - you're just getting a reference to it and then not doing anything with it...
Compare:
PS> $streamReader = [io.streamReader]::New(".\test.xml");
PS> $streamReader.Dispose
OverloadDefinitions
-------------------
void Dispose()
void IDisposable.Dispose()
PS> _
with
PS> $streamReader = [io.streamReader]::New(".\test.xml");
PS> $streamReader.Dispose()
PS> _
You need to add some () after the method name so your code becomes:
$xmlreader.Dispose()
$streamReader.Dispose()
And then it should release the file lock ok.

powershell how to add items to dictionary and then add the items in that list to another dictionary?

I am trying to parse values from an XML file that will add items to a collection using a foreach loop, then add the items from that collection to another collection using another foreach loop with an addition value. This is what I am doing so far:
[xml]$testResults = Get-Content -Path $testResultsPath
$resultsByName = #{}
$resultsByPhone = #{}
$loop = 0
foreach($testCase in $testResults.'test-results'.'test-suite')
{
foreach($testCase in $testResults.'test-results'.'test-suite'[$loop].'results'.'test-suite'.'results'.'test-suite'.'results'.
'test-suite'.'results'.'test-suite'.'results'.'test-suite'.'results'.'test-suite'.'results'.'test-case')
{
$NameWithPone = $testCase.name.ToUpper().Substring($testCase.name.LastIndexOf('.')+1);
$Name =$NameWithPone.Substring(0, $NameWithPone.IndexOf('_'));
$PhoneVersion = $testCase.name.Substring($testCase.name.IndexOf('_')+1);
$resultsByName.Add($PhoneVersion, $Name)
Foreach($resultCase in $resultsByName)
{
$resultsByPhone.Add($resultsByName, $testCase.result)
}
}
$loop++
}
But this will only add it first result, then give the error "Item has already been added. Key in dictionary:
'System.Collections.Hashtable' Key being added: 'System.Collections.Hashtable'" I think this is because I am adding the same item each time, how can I correct this?
The first collection will look like this:
google_pixel_xl-7_1_1 TESTTHATAREGISTEREDUSERCANLOGINTOTHECUSTOMERAPPSUCCESSFULLY
htc_10-6_0_1 TESTTHATAREGISTEREDUSERCANLOGINTOTHECUSTOMERAPPSUCCESSFULLY
oneplus_one-4_4_4 TESTTHATAREGISTEREDUSERCANLOGINTOTHECUSTOMERAPPSUCCESSFULLY
But I want to add both values together to another collection which would look like:
google_pixel_xl-7_1_1 TESTTHATAREGISTEREDUSERCANLOGINTOTHECUSTOMERAPPSUCCESSFULLY Error
I got this done by doing this:
[xml]$testResults = Get-Content -Path $testResultsPath
foreach($testCase in $testResults.'test-results'.'test-suite')
{
function Get-TestCases($myResults)
{
$testCases = #()
foreach($child in $myResults.ChildNodes)
{
if($child.'test-case' -eq $null)
{
foreach($testCase in Get-TestCases $child)
{
$testCases += $testCase
}
}
else
{
$testCases += $child.'test-case'
}
}
return $testCases
}
$tests = Get-TestCases $testResults.'test-results'.'test-suite'[$loop]
foreach($test in $tests)
{
$PhoneVersion = $test.name.Substring($test.name.IndexOf('_')+1);
$resultsByPhone.Add($PhoneVersion, #{})
$NameWithPhone = $test.name.ToUpper().Substring($test.name.LastIndexOf('.')+1);
$Name =$NameWithPhone.Substring(0, $NameWithPhone.IndexOf('_'));
$resultsByPhone[$phoneVersion].Add($Name, $test.result)
}
$resultsByPhone[$phoneVersion]
$resultsByPhone
$loop++
}

SugarQuery build up queryOr in a for loop

I am trying to build up a queryOr based on a comma separated list that is passed in by the user that I will loop over and add the values.
So far I got this which just builds it:
$query = new SugarQuery();
$query->from(BeanFactory::getBean('rdwrk_Request'));
$skills = $query->join('rdwrk_request_skills_rdwrk_request', array('alias' => 'skills'))->joinName();
$query->select(array(array('name', 'requestName'), array('skills.name', 'skillName')));
if($args["requestName"])
{
$requestName = $args["requestName"];
if(strpos($requestName, ',') !== false)
{
$requestNames = explode(",", $requestName);
foreach($requestNames as $name)
{
$query->where()->queryOr()->contains('name', $name);
}
}
else
{
$query->where()->contains('name', $requestName);
}
}
if($args["skillName"])
{
$query->where()->contains('skills.name', args["skillName"]);
}
$results = $query->execute();
Is there any way to build it so all the values I loop over go into the same queryOr?
You can set the current SugarQuery object into its own variable, and then feed it through your loop as follows:
$currentQuery = $query->where()->queryOr();
foreach($requestNames as $name)
{
$currentQuery->contains('name', $name);
}
This calls the contains function on the same query object with the queryOr established. Since the $currentQuery variable references the same SugarQuery object, running $query->execute() later on will include the parameters.

Update children nodes with PowerShell doesn't reflect in SharePoint GUI

I'm trying to update a node title on my SharePoint site with PowerShell:
$web = Get-SPWeb "http://dev:18792/sites/devsite/"
foreach ($webs in $web.Webs)
{
$nodes = $webs.Navigation.QuickLaunch
foreach ($node in $nodes)
{
$nodeTitle = $node.Title
if ($nodeTitle.ToString().Contains("First node"))
{
foreach ($nodeChild in $node.Children) {
$nodeChildTitle = $nodeChild.Title
if ($nodeChildTitle.ToString().Contains("Original childnode title")) {
$nodeChild.Title = "Changed title"
$nodeChild.Update()
Write-Host "done."
}
}
}
$webs.Dispose()
}
$web.Dispose()
}
If I try it a second time and change
.Contains("Original childnode title")
to:
.Contains("Changed title")
It does enter that if statement, but the changes is not visible in the GUI.
Am I missing something here?
Could it maybe be because you have the Publishing features activated? Manipulating the quick launch in a publishing site is a bit different. Here's some sample C# code:
PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(oWeb);
SPNavigationNodeCollection nodes = pubWeb.Navigation.CurrentNavigationNodes;
foreach (SPNavigationNode node in nodes)
{
if (node.Title == "Original Title")
{
node.Title = "New Title";
node.Update();
break;
}
}
Otherwise try calling the $webs.Update() method