What am I doing wrong on PowerShell function parameters? - function

I would really appreciate it if somebody could point out what I am doing wrong in passing parameters from a function back to the mainline code. I have a variable which has been successfully extracted in a function, but I cannot seem to pass that back to the mainline code
This is the code I am using:
function get-field ($field, $heading) {
$fieldPos = $script:source.AllElements.InnerText.IndexOf($heading) +1
$field = $script:source.AllElements.InnerText[$fieldPos]
# If states "Not Available", or contains a heading, process as if not found.
if ($field -eq "Not Available ") {$fieldPos = 0}
if ($field -eq $heading) {$fieldPos = 0}
# Check that a valid entry was received
if ($fieldPos -eq 0) {
Write-Host "Warning:" $heading "was not found"
} else {
$field = $field.Trim()
}
return $field
}
get-field $email "Name"
get-field $address "Address"
I have verified that within the function, the $field and $heading parameters contain the correct information, so why aren't the $email and $address fields being populated?

You're not doing it totally wrong.
Have a look at this example:
function get-field ($field, $heading) {
return "$field - $heading"
}
$address = get-field "AddressFiled" "AddressHeading"
$address
to catch the returned value in a variable for further use, you should call the function like in the above example.

Parameters in PowerShell are normally used for passing values into a function. The output of a function must be assigned to a variable in the statement that invokes the function. Also, it's bad design to use global variables inside a function, because that makes debugging significantly more difficult.
Your code should look somewhat like this:
function Get-Field ($data, $heading) {
$fieldPos = $data.IndexOf($heading) + 1
$field = $data[$fieldPos].Trim()
# If states "Not Available", or contains a heading, process as if not found.
if ($field -eq 'Not Available' -or $field -eq $heading) {
Write-Host "Warning: ${heading} was not found"
}
$field
}
$email = Get-Field $script:source.AllElements.InnerText 'Name'
$address = Get-Field $script:source.AllElements.InnerText 'Address'
You can have out parameters if you want to, but they're rather uncommon in PowerShell, probably because they're not as straight-forward to use as one would like.
function Get-Field ([ref]$field, $data, $heading) {
$fieldPos = $data.IndexOf($heading) + 1
$field.Value = $data[$fieldPos].Trim()
# If states "Not Available", or contains a heading, process as if not found.
if ($field -eq 'Not Available' -or $field -eq $heading) {
Write-Host "Warning: ${heading} was not found"
}
}
$email = $null
Get-Field ([ref]$email) $script:source.AllElements.InnerText 'Name'
$address = $null
Get-Field ([ref]$address) $script:source.AllElements.InnerText 'Address'

Related

Cant use variable from a function in another function

function User-Search($input)
{
Write-Host "Searching for user: $input"
pause
}
function Show-Menu
{
param (
[string]$Title = 'MainMenu'
)
cls
Write-Host "================ $Title ================"
Write-Host " "
Write-Host "Specify computer / username"
Write-Host " "
Write-Host "Q: Press 'Q' to quit."
Write-Host " "
}
do
{
Show-Menu
$input = Read-Host "Search"
User-Search -input $input
}
until ($input -eq 'q')
Outputs: "Searching for user:", it's empty.
There must be some small mistake i am doing, probably easy for you guys :)
$INPUT is an automatic variable:
Contains an enumerator that enumerates all input that is passed to a
function. The $input variable is available only to functions and
script blocks (which are unnamed functions).
So just use another variable, e. g. $user instead of $input

Powershell scripts work when run directly but not when called by another

I have a long list of simple jobs I would like to somewhat automate. It's simple stuff, grab or post info via API and build some reports, nothing fancy.
I decided to build a master script which directs out to a variety of other scripts, each handling its own job. Each one of those little scripts, reference functions from a Utility script which I built that has functions which are common to all the other simple job scripts.
Each of the scripts work perfectly when I run them directly, however, when I try to run them via the master script, which routes to them, they all fail.
One example is that in many cases I need to fetch data from an API but get capped at 1000 object returns when I need 10k+. To solve this, I built a function which recursively calls itself until there is no more data left to collect. Again, this works when called by itself but not from the master script, for some reason, it bails out after the first run (should run 10+ times in this case). Then, it returns nothing.
I am thinking maybe this has something to do with how I am scoping the functions/variables?? Not sure. I have tried scoping to Global, Local & Script but none seem to work. Here's some of the code...
*Master Director Script runs script based on user input*
...
&$choice_hash[$action].script_path
$ScriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
. "$ScriptDirectory\Utilities.psm1"
$user_data = $null
$env_choice = $null
$csv_output_path = $null
$collated_user_data = [System.Collections.ArrayList]#()
function selectEnv {
$global:env_choice = Read-Host #"
> Select an Environment: [Prod] or [Dev]
Your Choice
"#
if ($env_choice -ne 'Prod' -and $env_choice -ne 'Dev') {
consoleCmt $env_choice
consoleCmt 'Invalid Choice. Try again...'
selectEnv
} else {
if ($env_choice -eq 'Prod') {
$global:csv_output_path = '\\etoprod\******\Exports\Report_Users_Prod.csv'
} else {
$global:csv_output_path = '\\etoprod\******\Exports\Report_Users_Dev.csv'
}
$global:user_data = process_data $env_choice 'api/xm/1/people?embed=roles&limit=1000'
}
}
function processUsersData {
foreach($user in $user_data) {
$user_roles = ''
$role_divider = ','
for($i = 0; $i -lt $user.roles.data.length; $i++) {
# Only append a comma if there are more, otherwise leave blank for CSV deliniation
if ($i -eq $user.roles.data.length - 1) {
$role_divider = ''
}
$user_roles += $user.roles.data[$i].name + $role_divider
}
# Build ordered hash table with above data
$sanatized_user = [pscustomobject][ordered]#{id = $user.targetName; firstName = $user.firstName; lastName = $user.lastName; siteName = $user.site.name; roles = $user_roles }
# Shovel into storage array used for building the CSV
$global:collated_user_data += $sanatized_user
}
}
notice 'Initiating Groups Report Script'
selectEnv
processUsersData
exportCsv $collated_user_data $csv_output_path
Utility Script (relevant functions being called)
$res = $null
$content = #()
...
function process_data($env, $url) {
fetch_data $env $url
foreach($i in $res.data) {
$global:content += $i
}
if($res.links.next) {
fetch_more $env $res.links.next
}
return $content **Should return full collection of data, but fails after one pass**
}
function fetch_data($env, $url) {
$base = generateEnvBase $env
$path = "$base/$url"
$req = Invoke-WebRequest -Credential $cred -Uri $path -Method GET
$global:res = ConvertFrom-Json $req
}
function fetch_more($env, $url) {
$base = generateEnvBase $env
$path = "$base$url"
$req = Invoke-WebRequest -Credential $cred -Uri $path -Method GET
$res = ConvertFrom-Json $req
foreach($i in $res.data) {
$global:content += $i
}
if($res.links.next) {
fetch_more $env $res.links.next
}
}
Sorry in advance if I have not followed procedure or etiquette, I'm new here.
This should work if you declare all variables in Main.ps1 that are needed by functions. You could also use the "Script" scope when creating a new variable inside a function that you want to use outside the function. Example $Script:Var = "Stuff" created inside a function will be available to whole script.
Directory Structure
C:\Script\Root
| Main.ps1
\---Utilities
fetch_data.ps1
fetch_more.ps1
processUsersData.ps1
process_data.ps1
selectEnv.ps1
Main.ps1
#---[ Initization ]---#
# Strings
[String]$RootPath = $PSScriptRoot
[String]$UtilPath = "$($RootPath)\Utilities"
[String]$env_choice = $null
[String]$csv_output_path = $null
# Arrays
[Array]$user_data = #()
[Array]$content = #()
[Array]$collated_user_data = #()
[Array]$res = #()
#---[ Source in Utilities ]---#
# Get the scripts
$Utilities = Get-ChildItem -Path "$UtilPath" -File | Where-Object {$_.Extension -eq ".ps1"}
# Source in each one
foreach ($Item in $Utilities) {
.$Item.FullName
}
#---[ Select an Environment ]---#
# Get the User's choice
$env_choice = selectEnv
# Process the choice
switch ($env_choice) {
Prod {
$csv_output_path = '\\etoprod\******\Exports\Report_Users_Prod.csv'
$user_data = process_data 'Prod' 'api/xm/1/people?embed=roles&limit=1000'
}
Dev {
$csv_output_path = '\\etoprod\******\Exports\Report_Users_Dev.csv'
$user_data = process_data 'Dev' 'api/xm/1/people?embed=roles&limit=1000'
}
Test {
Write-Output "Test is not an option. Choose wisely."
exit 1
}
Default {
Write-Output "Unknown Environment Choice."
exit 1
}
}
#---[ Process Users and Export ]---#
processUsersData
exportCsv $collated_user_data $csv_output_path
selectEnv.ps1
function selectEnv {
$Title = "Environment:"
$Info = "Please choose an environment"
# Options
$Prod = New-Object System.Management.Automation.Host.ChoiceDescription '&Prod', 'Production environment'
$Dev = New-Object System.Management.Automation.Host.ChoiceDescription '&Dev', 'Development environment'
$Test = New-Object System.Management.Automation.Host.ChoiceDescription '&Test', 'Testing environment'
$Options = [System.Management.Automation.Host.ChoiceDescription[]]($Prod, $Dev, $Test)
$Default = 0
# Promp the User
$Choice = $host.UI.PromptForChoice($Title , $Info , $Options, $Default)
$Result = $Options[$Choice].Label -Replace '&',''
return $Result
}

Powershell - Store function output in variable not working

stuck at this one trying to store function output in a variable:
function AD-prompt($Text)
{
do
{
$in = read-host -prompt "$Text"
}
while($in -eq "")
}
calling the function with
$type = AD-prompt "Sample Text"
does not store anything in $type - only when i remove the entire do-while loop it works. it seems the function output is empty as the read-host output is stored in the $in variable, but i have no idea how to solve this - i didnt find another way to loop the read-host aswell sadly.
You need to return $in from your function by outputting it. You can do this by putting it on a line on its own after your loop:
function AD-prompt($Text)
{
do
{
$in = read-host -prompt "$Text"
}
while($in -eq "")
$in
}

PowerShell adds other values to return value of function

It seems that PowerShell adds an additional variable to the return value of a function.
The function subfoo2 itself delivers the correct values, but as soon as PowerShell jumps back to the postion where I called the function (in foo1), value contains the value of an other variable ($msg)
(Have a look at the comments in the code)
writeMessageLog($msg){
...
Add-Content $msg
...
}
subfoo2{
writeMessageLog($msg)
return $UserArrayWithValues #During Debug, $Array is fine (1)
}
foo1{
$var = subfoo2 $UserArray # $var has now the value of $msg and $UserArrayWithValues (2)
#do something with var
}
Realcode:
function WriteLog
{
param ( [string] $severity , $msgNumber, [string] $msg )
...
$msgOut = $date + ... + $msg
Add-Content $msgout ( $msgOut )
...
}
function getFeatures
{
writelog 'I' 1002 $true $true "Load Features"
$Features = importCsv -pPath $FeatureDefintionFilePath
Writelog 'I' 1000 $true $true "Features Loaded"
return $Features # $Features has value as expected (1)
}
function GetUserFeatures ($pUserObject)
{
$SfBFeatures = ""
$SfBFeatures = getFeatures #SfBFeaures has Value of $msg and $Features (2)
...
}
Do I use the functions/return values wrong? What could lead to such behavior? Is it an issue if i call a function within a function?
If I remove $msgOut = $date + ... + $msg in writeMessageLog, the values are fine.
I'm pretty lost right now, and have no ideas where this comes from. Any ideas welcome.
This is how powershell works, basically everything that you print out will be returned as the function output. So don't output extra stuff. To force something to not output stuff you can do:
$null = some-command_that_outputs_unwanted_things
since everybody is obsessed with Out-Null I'll add this link showing several other ways to do that.
Within a function, everything you don't assign or pipe to a consuming cmdlet will get put to the pipeline and returned from the function - even if you don't explicit return it. In fact the return keyword doesn't do anything in PowerShell so the following is equivalent:
function Test-Func
{
"Hello World"
}
function Test-Func
{
return "Hello World"
}
So it looks like your writeMessageLog puts anything on the pipeline thus you have to either assign the value to anything:
$notUsed = writeMessageLog($msg)
or (prefered) pipe it to the Out-Null cmdlet:
writeMessageLog($msg) | Out-Null

Using Set-ADUser within a Function

Im having issues setting an attribute value in AD when using a function. When I use Set-ADUser under the same conditions without using a function I do not get an issue, it works great. When using Set-ADUser within a function I am getting an invalid argument error. I need to use a function as I am comparing a lot of data values. Alot of data is going to be compared this the need for a function. Im stumped.
function compareandset($value_ad, $value_csv, $userid, $propdata) {
$id = $userid.SamAccountName
IF($value_ad -eq $value_csv) {
Write-Host "The values were the same!"
}
ELSEIF($value_ad -ne $value_csv) {
Write-Host "AD value changed"
get-aduser -filter {SamAccountName -eq $userid} | Set-ADUser -$propdata $value_csv
}
}
$userid = "jsmith"
$value_ad = "A city"
$value_csv = "Not a city"
$propdata = "Office"
compareandset $Office $office_csv_value $userid $propdata
What does the full error message say? You can try using splatting and change:
...
Write-Host "AD value changed"
get-aduser -filter {SamAccountName -eq $userid} | Set-ADUser -$propdata $value_csv
}
...
to:
...
Write-Host "AD value changed"
$params = #{$propdata=$value_csv}
get-aduser -filter {SamAccountName -eq $userid} | Set-ADUser #params
}
...
More about splatting here: http://technet.microsoft.com/en-us/library/jj672955.aspx