Powershell - Access global synchronized hash table from custom event handler - multithreading

I'm trying to access $syncHash inside the event handler scriptblock, but seems nothing happened. Is there a way to to it?
$syncHash = [hashtable]::Synchronized(#{})
$syncHash.PostPocess = {
[string]$path = $event.messagedata
...
# trying to access $syncHash here, but failed
...
}
Register-EngineEvent -SourceIdentifier Process_Result -Action $syncHash.PostPocess
New-Event -SourceIdentifier Process_Result -MessageData $path
Thanks,

Your event script block has no access to the initial PowerShell environment from which you have registered the event.
One solution can be to pass the synchronized hash through the MessageData handler of the event, here is your revised code for that :
$syncHash = [hashtable]::Synchronized(#{})
$syncHash.PostPocess = {
# Your $path variable is now in the first cell of the array $event.messagedata
[string]$path = $event.messagedata[0]
...
# Should display 'True', as $event.MessageData[1] is now your initial $syncHash
echo $event.MessageData[1].IsSynchronized
...
}
Register-EngineEvent -SourceIdentifier Process_Result -Action $syncHash.PostPocess
New-Event -SourceIdentifier Process_Result -MessageData #($path, $syncHash)
With an array, you can use the MessageData member of the $event as an arguments line.

Related

TCL multithreaded function call not getting executed using thread pool

the extract_system_kpis function is not being called - only the puts statements before are being printed.
proc process_all_scenarios_multithreaded {results_folder} {
package require dai
package require Thread
set scenario_list [::simulation::get_scenarios Simulation]
set curr_dir [pwd]
# use mulithreading to run scenarios in parallel via tcl thread pool
set pool [tpool::create -maxworkers 100 -initcmd {
proc process_scenarios {scenario} {
set sname [lindex $scenario 0]
puts "Scenario: $sname"
set sdir "$curr_dir/$results_folder/$sname"
puts "Results from: $sdir"
extract_system_kpis $sname "SUCCESS" $sdir
}
}]
foreach scenario $scenario_list {
lappend work [tpool::post -nowait $pool [list process_scenarios $scenario]]
}
# Wait until all threads complete
foreach id $work {
tpool::wait $pool $id
}
tpool::release $pool
}
I suspect the problem is that the package that provides extract_system_kpis (is that from dai?) is not not being loaded into the worker threads.
You probably need to make the thread pool with this:
set pool [tpool::create -maxworkers 100 -initcmd {
package require dai; # CRITICAL!
proc process_scenarios {scenario} {
set sname [lindex $scenario 0]
puts "Scenario: $sname"
set sdir "$curr_dir/$results_folder/$sname"
puts "Results from: $sdir"
extract_system_kpis $sname "SUCCESS" $sdir
}
}]
You're also super-dependent on the dai package being happy being loaded into multiple threads at once. It might be entirely happy, or it might not; there's no easy way to tell by inspection from outside, and it depends on how it is implemented.

Excel::Writer::XLSX Unreadable content error

I just started using Perl and I am using
Excel::Writer::XLSX
to query a DB2 database and export the data to an .xlsx file. The data is about 250k rows.
The script is running fine, but when I try to open the Excel file it throws an error and asks to repair the file. Upon repairing some of the data gets replaced by inf.
Below is a snippet from my code.
while ( my $sqlStatement = ) {
$mSQL = $dbh->prepare( $sqlStatement )
or die "Can't prepare $sqlStatement";
$mSQL->execute()
or die "Can't execute $sqlStatement";
}
my $workbook = Excel::Writer::XLSX->new( $ARGV[2] );
$workbook->set_tempdir( '/tempDir/' );
$workbook->set_optimization();
my $worksheet = $workbook->add_worksheet();
$worksheet->keep_leading_zeros();
my $row = 0;
my $column = 0;
my #emptyRow = ();
$worksheet->write_row( $row++, $column, [ #{ $mSQL->{NAME_uc} } ] );
$worksheet->write_row( $row++, $column, [ #emptyRow ] );
while ( my #Row = $mSQL->fetchrow_array ) {
$worksheet->write_row( $row++, $column, [ #Row ] ); #, $cellFormat);
$count++;
}
$workbook->close();
Can someone please advise me on this issue?
Finally i figured it out (Thanks to John McNamara). This was resolved by adding a write handler that uses regular expressions to check if a particular token is being converted to "inf", and if it does, it invokes the write_string subroutine instead of write_row.
Below is the code.
#!/usr/bin/perl
use strict;
use warnings;
use Excel::Writer::XLSX;
my $workbook = Excel::Writer::XLSX->new( 'write_handler5.xlsx' );
my $worksheet = $workbook->add_worksheet();
# Add a handler to match any numbers in order to check for and handle
# infinity.
$worksheet->add_write_handler( qr[\d], \&write_with_infinity );
# The following function is used by write() to pre-process any the data when a
# match is found. If it finds something that looks like a number but evaluates
# to infinity it write it as a string.
sub write_with_infinity {
my $worksheet = shift;
my #args = #_;
my $token = $args[2];
# Check if token looks like a number, in the same way as write().
if ( $token =~ /^([+-]?)(?=[0-9]|\.[0-9])[0-9]*(\.[0-9]*)?([Ee]([+-]?[0-9]+))?$/ ) {
# Check for infinity.
$token = $token + 0;
if ($token =~ /inf/) {
# Write the value as a string instead of a number.
return $worksheet->write_string( #args );
}
}
# Reject the match and return control to write()
return undef;
}

perl threads create - how to correctly specify class instance method?

Having problems with threads. Keep getting error when creating a thread using a class instance method as the subroutine. The method and params variables are set based on other stuff, so I have to call the class instance method this way. Without the threads, it works just fine. Can't figure out the correct way to specify it for threads create:
my $instance = someclass->new();
my $method = 'get';
my $params = { 'abc' => 123 };
my $thread = threads->create($instance->$method,$params);
This gives me the error "Not a CODE reference". I think this may be actually calling the method, and using the return as the argument. Okay, tried this:
my $thread = threads->create(\&{$instance->$method},$params);
This gives me the error "Not a subroutine reference". I would appreciate any help on this.
my $thread = threads->create(sub { $instance->$method(#_) }, $params);
Or, you could just pass the instance and the method to the first argument as well:
package SomeClass;
sub new {
my $class = shift;
bless { args => [ #_ ] };
}
sub get {
my $self = shift;
my $args = shift;
return join(" ", #{ $self->{args} }, $args->{abc});
}
package main;
use 5.012;
use threads;
my $x = SomeClass->new("An instance");
threads->create(sub { say $x->get(#_) }, {'abc' => 123 })->join;
threads->create(
sub {
my $instance = shift;
my $method = shift;
say $instance->$method(#_);
}, $x, 'get', { 'abc' => 123 }
)->join;
In fact, I would prefer the latter, to avoid closing on $instance.
Calling a method without parens is the same thing as calling the method without arguments:
$foo->bar eq $foo->bar()
To create a coderef, you can either specify a lambda that wraps the method call, e.g.
threads->create(sub{ $instance->get($params) })
(see Sinan Ünürs answer), or you can use the universal can function.
The can method resolves a method in the same way a method would be resolved if it were called, and returns the coderef for that method if it was found, or returns undef. This makes it usable as a boolean test.
Do note that methods are just subroutines with the first argument being the invocant (the object):
my $code = $instance->can($method) or die "Can't resolve $method";
threads->create($code, $instance, $params);
However, can may fail for poorly written classes that make use of AUTOLOAD.

Update SharePoint quicklaunch link URL through PowerShell

The setup is MOSS2007. I iterate the links in the QuickLaunch, and update the URL:
$siteUrl = "http://myserver/"
$spSite = new-object Microsoft.SharePoint.SPSite($siteurl)
for($i=0; $i -lt $spSite.AllWebs.Count;$i++)
{
$spWeb = $spSite.AllWebs[$i]
$nodes = $spWeb.Navigation.QuickLaunch
for($j=0; $j -lt $nodes.Count;$j++)
{
$children = $nodes[$j].Children
for($k=0; $k -lt $children.Count;$k++)
{
$x = $children[$k]
$x.Url = "http://mylink/"
$x.Update()
}
}
$spSite.Dispose();
}
But the Doclib URLs don't update. If I go to Site settings -> Navigation -> and edit the URL through the UI, then run my script again, the URL updates. Why can't I manipulate the URL through code?
I'm not sure if this is the answer, but it looks to me like your Dispose is in the wrong place. It should be outside the outer foreach, i.e. at the same nesting level as your $spSite assignment. This repeated dispose may be causing sync problems.

How do I do a string replacement in a PowerShell function?

How do I convert function input parameters to the right type?
I want to return a string that has part of the URL passed into it removed.
This works, but it uses a hard-coded string:
function CleanUrl($input)
{
$x = "http://google.com".Replace("http://", "")
return $x
}
$SiteName = CleanUrl($HostHeader)
echo $SiteName
This fails:
function CleanUrl($input)
{
$x = $input.Replace("http://", "")
return $x
}
Method invocation failed because [System.Array+SZArrayEnumerator] doesn't contain a method named 'Replace'.
At M:\PowerShell\test.ps1:13 char:21
+ $x = $input.Replace( <<<< "http://", "")
Steve's answer works. The problem with your attempt to reproduce ESV's script is that you're using $input, which is a reserved variable (it automatically collects multiple piped input into a single variable).
You should, however, use .Replace() unless you need the extra feature(s) of -replace (it handles regular expressions, etc).
function CleanUrl([string]$url)
{
$url.Replace("http://","")
}
That will work, but so would:
function CleanUrl([string]$url)
{
$url -replace "http://",""
}
Also, when you invoke a PowerShell function, don't use parenthesis:
$HostHeader = "http://google.com"
$SiteName = CleanUrl $HostHeader
Write-Host $SiteName
Hope that helps. By the way, to demonstrate $input:
function CleanUrls
{
$input -replace "http://",""
}
# Notice these are arrays ...
$HostHeaders = #("http://google.com","http://stackoverflow.com")
$SiteNames = $HostHeader | CleanUrls
Write-Output $SiteNames
The concept here is correct.
The problem is with the variable name you have chosen. $input is a reserved variable used by PowerShell to represent an array of pipeline input. If you change your variable name, you should not have any problem.
PowerShell does have a replace operator, so you could make your function into
function CleanUrl($url)
{
return $url -replace 'http://'
}
function CleanUrl([string] $url)
{
return $url.Replace("http://", "")
}
This worked for me:
function CleanUrl($input)
{
return $input.Replace("http://", "")
}

Resources