powershell add objects via -outvariable +variable in a function - function

I am running in an imported session window. Not sure if that matters.
I am trying to add a few variable values to an array in a function.
$Session = New-PsSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://emailserver/powershell/" -Authentication Kerberos
Import-PsSession $Session -allowclobber
Add-PsSnapin Microsoft.SharePoint.PowerShell
$group1 = "Accounting"
$group2 = "HR"
function AddUsersToGroups {
Write-output $Group1 -outvariable +Adgroups
Write-output $Group2 -outvariable +Adgroups
}
When I highlight and run the lines from a Powershell ISE they work fine and create the Variable $AdGroups and the combined data is in there.
But when I run the function all I see is the output and no variable gets created.
PS C:\Windows\system32> AddUserToGroups
Group1
Group2
Kinda Stumped. I tried to create a variable $AdGroups = #() as the first line but it fails also.

Not sure exactly what you are trying to achieve, but the AdGroups variable will be available only in the scope of the AddUsersToGroups function.
One way of solving this would be add the script: scope, like below:
Function AddUsersToGroups{
Write-output $Group1 -outvariable +script:Adgroups
Write-output $Group2 -outvariable +script:Adgroups
}
Now you should be able to access $Adgroups outside after the function is called.

Related

Powershell startjob import-module call function with arguments

So I am trying to start-job from a module I wrote.
Copy-Modules.psm1
function startcopy([string] $ShowToCopy) {
if (-not($ShowToCopy)) { return "No name provided. Doing nothing." }
} else { return "Name Provided $ShowToCopy" }
}
in the main script I am calling it as follows:
$Copyname = "test"
Start-Job -Name "copy1" -InitializationScript { Import-Module -Name .\Copy-Modules.psm1 } -ScriptBlock {startcopy} -ArgumentList $Copyname
However the arguments never seems to go through. No matter how I format or pass the argument with switch or without I always get the result No name provided. Doing nothing.
The simplest solution - assuming you need no other functions from your Copy-Modules.psm1 module - is to pass your function's body as Start-Job's -ScriptBlock argument:
Start-Job -Name "copy1" -ScriptBlock $function:startcopy -ArgumentList $Copyname
$function:startcopy uses namespace variable notation to get the startcopy's body as a script block.
Note:
This obviates the need to define your startcopy function in the scope of the background job (which is an independent session in a child process that knows nothing about the caller's state), which is what your -InitializationScript script block does.
The only limitation of this approach is that the script block won't be named, i.e. the original function name is lost, and $MyInvocation.MyCommand.Name inside the function returns the empty string.
As for what you tried:
It is the script block as a whole that receives the (invariably positional arguments passed to -ArgumentList, which you'll have to pass on explicitly to any commands called inside the script block, using the automatic $args variable:
$Copyname = "test"
# Note the use of $args[0]
Start-Job -Name "copy1" `
-InitializationScript { Import-Module -Name .\Copy-Modules.psm1 } `
-ScriptBlock { startcopy $args[0] } -ArgumentList $Copyname

How to pass arguments to functions in PowerShell

I have the below PowerShell script
Function Publish
{
Param(
[parameter(Mandatory=$true)]
[String]
$RELEASEDIR,
[parameter(Mandatory=$true)]
[String]
$SERVICENAME,
[parameter(Mandatory=$true)]
[String]
$SERVER
)
Get-ChildItem "$RELEASEDIR\*"
$service = Get-Service -Name $SERVICENAME -Computername $SERVER -ErrorAction SilentlyContinue
$service.Status
}
Publish
How I am executing this:
PS C:\Release\RPCPS> .\RPCPublish.ps1 -RELEASEDIR "C:\Location" -SERVICENAME "value" -SERVER "server"
cmdlet Publish at command pipeline position 1
Supply values for the following parameters:
RELEASEDIR:
Even after passing arguments while executing, the script is expecting it again. What am I doing wrong here?
If you want to execute the script by calling the .ps1 as in your example, there is no need to use a function. Your script should look just like this:
Param(
[parameter(Mandatory=$true)]
[String]
$RELEASEDIR,
[parameter(Mandatory=$true)]
[String]
$SERVICENAME,
[parameter(Mandatory=$true)]
[String]
$SERVER
)
Get-ChildItem "$RELEASEDIR\*"
$service = Get-Service -Name $SERVICENAME -Computername $SERVER -ErrorAction SilentlyContinue
$service.Status
The parameters are passed directly to the script and can be used there.
If, on the other hand, you want to establish a (reusable) function, remove just the last line from your script, which calls the function without parameters (which is why it asks for the mandatory parameters every time).
If you remove the last line, you can call the script without parameters once. After that you have a new function Publish in your current session, which you can then call with
Publish -RELEASEDIR "C:\Release\Batchfile" -SERVICENAME "AmazonSSMAgent" -SERVER "10.0.1.91"
independent of the script file.
Your script is creating a function, "Publish", (lines 1-17) and then calling it with no parameters (line 18). Since you've defined parameters as mandatory (lines 4, 7, 10), failing to supply the parameters when you call the function (line 18) causes PowerShell to request values for the unsupplied parameters.
Supplying parameters to the script file itself does not help; there is no mechanism for "automagically" passing those parameters to anything within the script (you would have to explicitly code the script for that).
As Matt suggested in the comments, dot-source your script after deleting line 18, and then call your function explicitly, passing the parameters (publish -RELEASEDIR "C:\Release\Batchfile" -SERVICENAME "AmazonSSMAgent" -SERVER "10.0.1.91").
As per my understanding your requirement is to run the function, and you have to compile the scripts also in Jenkins.
You can do something like this:
Let's say your script name is RPCPublish.ps1 and the path is D:\Folder.
And I can see your function name is Publish.
So in your case,
powershell -command "& { D:\folder\RPCPublish.ps1; Publish }"
You can pass the parameters after this in the script block.
I used a PowerShell plugin (PowerShell) and executed the same.
. "C:\Release\RPCPS\RPCPublish.ps1"
FUunctionName -RELEASEDIR "C:\bin\Release" -SERVICENAME "Service" -SERVER "$env:SERVER" -DISPLAYNAME "Services Air" -BINPATH "D:\Build\Project.exe" -DESCRIPTION "This service hosts Air service" -DESTINATION "d$\Build\"

Passing a [ref] parameter in a remote session in Powershell

I have a Powershell question.
I am trying to get a value from a function to a variable, by calling a function with a reference to the variable.
For example:
$var = New.Object System.Object;
Example-Function -OutObject ([ref]$var);
Where the Example-Function is defined like this:
function Example-Function
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true)]
[ref]
$OutObject
)
$SomeValue = ...
#Write some output
#Do something...
$OutObject.Value = $SomeValue;
}
This is working OK. The $var variable gets it's value from the function ($SomeValue).
But, this is not working when the Example-Function is imported into remote session, for example:
$creds = New-Object System.Management.Automation.PSCredential('user','pass')
$session = New-PSSession -ComputerName 'ExampleComputer' -Credential $creds -Authentication CredSSP
Import-PSSession -Session $session -CommandName 'Example-Function' -AllowClobber
$var = New.Object System.Object;
Example-Function -OutObject ([ref]$var);
This code is throwing the following error: Cannot process argument transformation on parameter 'OutObject'. Reference type is expected in argument.
I am assuming that this is becuase the Example-Function is now running on the other computer ('ExampleComputer'), while ([ref]$var) is referencing the variable in memory of the computer running the scripts (my computer).
The reason I don't want to (cannot) use the return statement way is becuase my function is writing some output, and in Powershell, everything that is outputed from a function is returned.
So, my question is, can I get a value from a function that has a lot of output into the variable, when the function is running in the remote session?
If it cannot be done by using the [ref] parameter, is there another way?
Thanks
Okay lets try again:
Invoke-Command returns whatever is run in the remote pipeline. Which means you can do:
$var = Invoke-Command -session $session -command {Example-Function}
Which saves everything in the $var variable. You can then filter the results and get whatever information you need.
And please remember [ref] just makes everything more complicated than it actually is.

PowerShell Repeatedly calling a function

The task at hand is to compare permissions from a source folder with a target folder, and this for all its sub folders. I've already created the function that does this check on one folder, which returns $True or $False.
I would like to know if it's possible to create a function that calls itself to be executed on every sub folder it finds to call Test-ACLequalHC. So that when it blocks or errors out in one of the sub folders, due to permission issues or something else, it can still continue with the others.
Something like a crawler, if that makes sense. Ideally it would be great if it could run in parallel. I read that a Workflow is most suited for this, but I've never used it before.
Unfortunately it's not possible to just do $AllSubFolders = Get-ChildItem -Recurse followed by a foreach, because there are over thousands of files and folders under the root folder. So it needs to be dynamically so that we can do extra stuff on every folder it finds, like say if Test-ACLequalHC results in $False on one folder, we can still call other functions to set the permissions correct or add the result to a CSV.
Permission test:
Function Test-ACLequalHC {
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[parameter(Mandatory=$true,Position=0)]
[ValidateScript({Test-Path $_})]
[String]$Source,
[parameter(Mandatory=$true,Position=1)]
[ValidateScript({Test-Path $_})]
[String]$Target,
[parameter(Mandatory=$true,Position=2)]
[ValidateSet('Access','Owner','All')]
[String]$Type
)
Begin {
$Props = Switch ($Type) {
'Access' {'Access', 'AreAccessRulesProtected'}
'Owner' {'Owner'}
'All' {'sddl'}
}
}
Process {
$CompParams = #{
Property = $Props
ReferenceObject = Get-Acl $Source #| Sort-Object
DifferenceObject = Get-Acl $Target #| Sort-Object
PassThru = $True
}
$Result = Compare-Object #CompParams
if ($Result -ne $null) {
Write-Output $false
}
else {
Write-Output $True
}
}
}
It would be great if it could check files to, for inheritance and that no extra permissions are added. But I'll add that stuff later on myself if I find out how to make such a crawler thingy that digs its way through the folder structure.
Thank you for your help.
Ok, I think your aversion of doing Get-ChildItem -Recurse is something you're going to need to get over. If you are only looking at directories use the -directory switch for Get-ChildItem. It's a provider level switch so it will speed things up dramatically.
Next, what I think you need to consider is the Ad--Member cmdlet. Something like this:
$Source = C:\GoodFolder
$AllFolders = GCI C:\ -Directory -Recurse
$AllFolders | ForEach{Add-Member -InputObject $_ -NotePropertyName "ACLGood" -NotePropertyValue (Test-ACLequalHC $source $_.fullname all)}
Then you can just filter on that for folders that have issues and address them as needed.
$AllFolders | Where{!$_.ACLGood} | ForEach{ Do stuff to fix it }

PowerShell and global functions

Why is the following code not working? According to this article the usage of global should be correct: http://technet.microsoft.com/en-us/library/ff730957.aspx
Function global:writeLog {
param($logType, $logString, $logFile)
$fileStream = New-Object IO.FileStream $logFile ,'Append','Write','Read'
$streamWriter = New-Object System.IO.StreamWriter $fileStream
$time = get-date -Format "hh:mm:ss"
$streamWriter.writeLine("[${time}][$logType] ${logString}")
$streamWriter.close()
}
$temp = {
writeLog -logType "INFO" -logString "Test" -logFile "d:\scripts\powershell\logtest.txt"
}
Start-Job -ScriptBlock $temp
get-job | receive-job -AutoRemoveJob -Wait
This is the exception that powershell throws
The term 'writeLog' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included,
verify that the path is correct and try again.
+ CategoryInfo : ObjectNotFound: (writeLog:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
+ PSComputerName : localhost
From the documentation of Start-Job:
A Windows PowerShell background job runs a command "in the background" without interacting with the current session.
Therefor, the current session scope is ignored.
Trivial Solution: Define the function inside the scriptblock.
$JobScript = {
function write-log {
....
}
write-log <parameters>
}
Alternatively, check these related questions:
Powershell: passing parameters to a job
Variables in Start-Job
PowerShell jobs actually run in a separate PowerShell process. You can see this like so:
$pid
Start-Job {$pid} | Receive-Job -Wait
Where $pid is the current PowerShell's process id.
Anything that needs to be accessed from the script that runs in the job, must be either defined in the scriptblock passed to Start-Job i.e. function defined in the script block or as parameters passed into the script block using the -ArgumentList parameter on Start-Job or the script can dot source another script (or import a module) that contains the functions it needs. Personally, I would put shared functions in a module like Utils.psm1 and then import like so:
Start-Job {param($scriptdir) Import-Module $scriptdir\Utils.psm1; ...} -Arg $PSScriptRoot
Define the function in a script block, then use
Invoke-Command with NoNewScope to get it in the current scope
The InitializationScript parameter to get it into the job
#Create Shared Functions Script Block
[scriptblock] $func = {function getPID() {write-output "Function Running in process id: $pid!"}}
#Set up functions in normal script scope so they're accessible here
Invoke-Command -NoNewScope -ScriptBlock $func
write-output "Main script started"
#run the function from the parent script
getPID
#Create background job script
$jobScript = {getPID}
#Run background job
write-output "starting background job"
Start-Job $jobScript -name "Job1" -InitializationScript $func
get-job | Receive-Job
get-job | Stop-Job
get-job | Remove-Job