At the moment I am trying to pass in two array items by reference to a function I have written which loads data into those arrays. However, once this function loses scope the arrays appear as blank.
If I use the ref keyword to pass them into the function the first array loads up correctly. However, the second array gives an error saying that I cannot use the add operator on it.
$logConfigPath = "C:\Testing\Configuration\config.xml"
#### VARIABLES RELATING TO THE LOG FILE
# Contains the log path and log file mask
$logPaths = #()
$logFileMasks = #()
#### FUNCTION CALLS
LoadLogTailerConfig($logConfigPath, $logPaths, $logFileMasks)
"$logPaths"
"$logFileMasks"
function LoadLogTailerConfig($logConfigPath, $logPath, $logFileMasks)
{
Write-Debug "Loading config file data from $logConfigPath"
[xml]$configData = Get-Content "C:\Testing\Configuration\config.xml"
foreach ($log in $configData.Logs.Log) {
$logPaths += $log.FilePath
$logFileMasks += $log.FileMask
}
}
Why is this not working for me?
I modified your example to work:
$logConfigPath = "C:\Testing\Configuration\config.xml"
#### VARIABLES RELATING TO THE LOG FILE
# Contains the log path and log file mask
$logPaths = #()
$logFileMasks = #()
function LoadLogTailerConfig($logConfigPath, [ref]$logPaths, [ref]$logFileMasks)
{
Write-Debug "Loading config file data from $logConfigPath"
#[xml]$configData = Get-Content "C:\Testing\Configuration\config.xml"
foreach ($log in 1..10) {
$logPaths.value += $log
$logFileMasks.value += $log
}
}
#### FUNCTION CALLS
LoadLogTailerConfig $logConfigPath ([ref]$logPaths) ([ref]$logFileMasks)
"$logPaths"
"$logFileMasks"
Notes:
Different syntax for calling functions in PowerShell
You need to define your function first then call not the other way around
Make sure that you use correct parameter names. Your functions accept $logPath, but then it tries to modify $logPaths - of course that's not going to work as expected because of the extra s at the end
You need to use [ref] both in the function definition and function call
You need to access reference value by adding .value to the reference variable
Also refer to the almost identical prior question here: Powershell passing argument values to parameters and back
You are complaining about a problem that's not the first problem in your code. You are using the function LoadLogTrailerConfig without defining it before its usage. So, you must correct this gaffe first. After the correction, your code will run without syntax error, but logically it will still present an undesired output, but the explanation will then, be simple: array is an imutable reference object.
Related
I have a very weird problem happening. I have an object populated with a bunch of rows. I can access them all well, but I need to append a "." (dot) to every value inside it, so I end up converting each record to a string using a for each loop and adding a "." after trimming the values. The issue now however, is that I would like to assign each of these rows (And the rows have only one column/Item) to another array/object, so I can access them later.
The issue is that even when I declare an object/array variable and assign the string converted data rows, the object variable keeps getting converted to a String, and I just cant seem to avoid it.
Please help. Here is a modified code sample:
[String] $DBNms = #();
ForEach($DBName in $objDBNms){
$tempText += $DBName.Item(0).ToString().Trim() + "."
$DBNms += $tempText
}
Write-Host($DBNms.GetType()) #And this is showing up a string, while I want it to be an array.
And if I print $DBNms, it indeed shows up a string concatenated together into a single string, while I actually want it to be like $DBNms[0] = first Item value, $DBNms[1] = second Item value and so on.
[String] $DBNms = #(); makes $DBNms a type-constrained variable, due to type literal [string] being placed to the left of variable $DBNms, whose type is then locked in as a string; that is, as a single string.
You were looking to create a string array, which in PowerShell is represented as [string[]]:
[string[]] $DBNames = #()
PowerShell's type conversions are automatic (and very flexible), so that [String] $DBNms = #() doesn't report an error, it quietly converts the empty array (#()) to the empty string (as required by the type constraint):
PS> [string] $DBNames = #(); '' -eq $DBNames
True
A much more efficient way to collect values from multiple iterations in an array is to use the foreach statement as an expression, which case PowerShell collects the outputs automatically for you:
[string[]] $DBNms = foreach ($DBName in $objDBNms){
# Output this expression's value as-is.
# PowerShell will collect the individual iterations' values for you.
$DBName.Item(0).ToString().Trim() + "."
}
The above is not only more concise than your original approach, it is more importantly more efficient:
In reality you cannot directly add (append to) an array, because it is an immutable data structure.
What PowerShell has to do whenever you use += with an array is to allocate a new array behind the scenes, with the original elements copied over and the new element(s) appended; the new array is then automatically assigned back to the variable.
Note: The alternative and next best solution to using the whole loop as an expression is to use an efficiently extensible list type, notably [System.Collections.Generic.List[object]](System.Collections.Generic.List`1):
# Create the list.
# The 'System.' namespace prefix is optional.
[Collections.Generic.List[string]] $DBNms = #()
foreach ($DBName in $objDBNms) {
# Add to the list, using its .Add() method.
# Note: Do NOT try to add with +=
$DBNms.Add($DBName.Item(0).ToString().Trim() + ".")
}
You'll need this approach if your loop does more than outputting a single value per iteration, such as needing to append to multiple collections.
Note: It used to be common to use System.Collections.ArrayList instances instead; however:
Use of this type is no longer recommended (see the warning in the linked help topic); use [System.Collections.Generic.List[object]] instead, which additionally allows you to strongly type the collection (replace [object] with the type of interest, such as [string] in the code above).
It has the drawback of its .Add() method having a return value, which you need to silence explicitly (e.g., $null = $lst.Add(...)), so that it doesn't accidentally pollute your code's output stream (produce unexpected extra output).
I'm attempting to add performance to a script that has an array of data of over 10,000 entries, then use it in a foreach-object statement to fill a blank ArrayList with new data by calling another function. I've been reading how I shouldn't use +=, which is how I learned, because the performance is dreadful as it tears down the array and rebuilds it for each item.
The issue I have is I need to call a function to fill an empty ArrayList, but I don't seem to be able to do this inside the .Add() method.
Old code:
Function get_gfe
Function get_os
$gfe = [System.Collections.ArrayList]#()
$gfe = get_gfe
$getos = [System.Collections.ArrayList]#()
$gfe | foreach { $getos += get_os $_}
This takes over an hour to fill $getos with the data.
I was hoping to use something like this instead, but it doesn't work, any help would be appreciated
$gfe | foreach { [void]$getos.Add(get_os $_)}
I know that you can use .Add($_), but that doesn't meet my needs and I couldn't find any references to using other code or calling functions inside the .Add()method.
Thanks in advance!
Why not expand the foreach-loop to something like this:
foreach ($entry in $gfe){
$os = get_os $entry
[void]$getos.add($os)
}
A foreach-loop also saves time compared to | piping into foreach-object.
Although of course since I don't know what your functions are actually doing, this could not be the most effective way to save time. You can determine that with measure-command.
Is it absolutely vital that $getos is of type System.Collections.ArrayList instead of a 'normal' array (System.Object[]) ?
If not, I think the next code could perform faster:
$getos = foreach ($entry in $gfe) {
get_os $entry # output the result of function get_os and collect in variable $getos
}
Thanks to all for the recommendations, they've helped me to gain a better understanding of foreach, arrays, and arrayLists. We've suspect the slowness is related to the foreach loop accessing a function, which uses an API for each serial number. We had recently upgrade our MDM console and swapped out the underlying hardware.
So I have an object that contains an array:
package MyObject;
sub new {
my($type) = #_;
my $self->{Params}{Status}{Packages} = [];
}
I have a add new package sub which appends onto this "Package" array like:
sub add_package {
my($self, $package_obj) = #_;
push $self->{Params}{Status}{Packages}, $package;
}
Now when I go to find all the packages in my array I have issues. Whenever I try and pull out the packages like this:
foreach my $package($self->{Params}{Status}{Packages}) {
# do something with $package.
}
This only loops through one time. Now from what I understand the hash actually stores a pointer to the array so I tried to do:
foreach my $package(#$self->{Params}{Status}{Packages}) {
# do something with $package.
}
But then there is an error saying that $self is not an array. I did notice when I do:
scalar $self->{Params}{Status}{Packages};
It returns:
#ARRAY(0xSome Address);
What am I missing? And how can I use a foreach loop to go through my array?
$self->{Params}{Status}{Packages} is a reference to an array, in Perl terminology. When you have a reference to something, put the right character in front of it to dereference it. If the reference is more than just a name with possibly some sigils in front, you need to surround it with braces. It's a matter of precedence: #$self->{Params}{Status}{Packages} is parsed as (#$self)->{Params}{Status}{Packages}, but you need
#{$self->{Params}{Status}{Packages}}
i.e. the array referenced by the expression $self->{Params}{Status}{Packages}.
In this case, you need to wrap it all in the array dereference block #{} so perl knows which portion you're trying to dereference...
for my $package (#{ $self->{Params}{Status}{Packages} }){
print "$package\n";
}
Also, just to keep things consistent, I prefer to always deref the array with the block when extracting, or inserting:
push #{ $self->{Params}{Status}{Packages} }, $package;
UPDATE: As of 5.24.0+, autoderef (using keys(), values() or each() with a reference) will almost certainly be removed, and replaced with postfix references. However, using the #{} and %{} will continue to be supported, and is backwards compatible, so I'd recommend using them at all times.
In my view, the clearest way to do this is to extract the array reference to a temporary scalar variable, which makes accessing the array very straightforward
my $packages = $self->{Params}{Status}{Packages};
for my $package ( #$packages ) {
# do something with $package.
}
Also, if you have use strict and use warnings enabled as you should, your add_package subroutine will produce the message
push on reference is experimental
This isn't something you can safely ignore. Experimental features may change their behaviour or disappear completely in later versions of Perl, and it is unwise to make use of them in production code. You can fix your subroutine in a similar way, like this
sub add_package {
my ($self, $package_obj) = #_;
my $packages = $self->{Params}{Status}{Packages};
push #$packages, $package;
}
I have a script I use to manage some Exchange attributes. I recently added some code to handle setting proxy addresses. I use a function to build the list then return a collection object to set the list in a different function. this is the jist of that function:
function buildProxyAddresses([string]$user)
{
$addressCollection = New-Object -TypeName Microsoft.ActiveDirectory.Management.ADPropertyValueCollection
$addressCollection.Add("smtp:" + $user + $domain)
#etc
#etc....
Return #(,$addressCollection)
}#endFunc buildProxyAddresses
Took me a while but I figured out how to pass the object by sticking it in array when I return it, ugly but functional. works fine, I can access the object by calling a $returnvar.item(3) on the return variable. where the third element is the ADPropertyValueCollection
Now I take the same script to my Co-Workers computer and he runs it and he gets an error that tells him :
[System.Object[]] doesn't contain a method named 'item'.
I have no idea why it runs different on his machine
Try using:
$returnvar[3]
It's likely its failing on a machine that's running an older version of powershell. Looks like the .item(x) syntax works from version 3 upwards.
However, it's not a normal way to reference an array index, the standard is to use $array[idx]
I am creating an invoice on users desired package selection. The pdf file is being created (but it takes some time), while the code checks for the file. The file exists. Here is the address of the file;
C:/wamp/www/proposal/file/invoice/Basic_52_60.pdf
This is the correct path to the file. I am passing this path to the function in another controller as;
redirect('email/email_invoice/'.$file);
When I tested the file path in email_invoice function, it displayed only c:
c:
The slashes in the path are not transferred. I don't know exactly what is the problem.
CodeIgniter considers each segment of the URL a parameter after the controller and method. So you are essentially passing 7 variables to the Email::email_invoice() method.
You could use some sort of encoding to pass it as one variable and then decode it on the other side such as:
$file = base64_encode($file);
redirect('email/email_invoice/' . $file);
Then in Email.php:
public function email_invoice($file) {
$file = base64_decode($file);
}
Or you could pass it as a get parameter:
redirect('email/email_invoice/?file=' . $file);
public function email_invoice() {
$file = $this->input->get('file');
}
The latter requires the $_GET array to be enabled which it is not by default.
UPDATE - Using Flashdata
Based on some of the comments I thought I would update this answer. base64_encode() can result in characters that will break the URL so you would need to use:
$file = urlencode(base64_encode($file));
redirect('email/email_invoice/' . $file);
And on the other side:
public function email_invoice($file) {
$file = urldecode(base64_decode($file));
}
As the OP pointed out $_GET variables can be manipulated leaving you open to directory traversal attacks or other vulnerabilities. Even if done right you would need extra code for security. Encoding can easily be spotted and altered.
File paths probably shouldn't be carried around in the URL. POST data can be manipulated also even if it is less obvious. Security through obscurity is not security at all. A better approach would be to use flashdata.
$this->session->set_flashdata('email_invoice_pdf', $file);
redirect('email/email_invoice/');
Then in your controller:
public function email_invoice() {
$file = $this->session->flashdata('email_invoice_pdf');
}
That's it. The session was used to carry the file path to the next page request, but after that it is gone.