How do I reliably get variable from child - powershell

I am building a sql Job Generator in Powershell. My PS skills aren't the greatest,
I want to get a value created in function CreateSqlTask. The variable I want is $job. I get an array of objects back from this function.
#My call
$returnParams = CreateSqlTask ( LIST OF PARAMS)
$returnParams[0] is the value of the jobSchedule Creation $returnParams[1] is the variable I want, this is the value of $job .
as a programmer I do not believe it is relable to just assume $returnParams[1] is always the variable I need. What is the proper way to handle this case?
#Here is the function implementation:
function CreateSqlTask
{
Param ( LIST OF PARAMS )
#Make all errors terminating
$ErrorActionPreference = "Stop"
#Create the SQL Job
$job = CreateSqlJob -serverInstance $serverInstance -jobName $jobName -jobDesc $jobDesc -jobCategory $jobCategory -jobAlertOperator $jobAlertOperator -jobEmailLevel $jobEmailLevel
#Create the SQL Job Step
$jobStep = CreateSqlJobStep $job $stepName $stepCmd
#Alter the Job to tell it what step should execute first
$job.StartStepID = $jobStep.ID
$job.Alter()
#Create the SQL Job Schedule
CreateSqlJobSchedule $job `
$schedName `
$schedFreqType `
$schedFreqRecurFactor `
$schedFreqInterval `
$schedFreqSubDayType `
$schedFreqSubDayInterval `
$startingSchedHour `
$startingSchedMinute `
$endingSchedHour `
$endingSchedMinute
return $job
}

A function should return only one type of object. If you don't need the job creation return, you can send that output to $null or assign it to a variable within the function. If you do need that along with the other information I'd create a custom object or hash table that includes that information along with the job information and return that.

As you have pointed out, both CreateSqlJobSchedule and return $job are returning values. If you need both of these then I suggest you assign these to a new object:
$jobDetails = #{
CreateSqlJob = $CreateSqlJob
CreateSqlJobSchedule = $CreateSqlJobSchedule
}
return $jobDetails
The above assumes you have assigned the two calls to two variables, you can then refer to them by name:
$createSqlTaskResults = CreateSqlTask
$createSqlTaskResults.CreateSqlJob
$createSqlTaskResults.CreateSqlJobSchedule
Here's how it would look in your example:
#Here is the function implementation:
function CreateSqlTask
{
Param ( LIST OF PARAMS )
#Make all errors terminating
$ErrorActionPreference = "Stop"
#Create the SQL Job
$CreateSqlJob = CreateSqlJob -serverInstance $serverInstance -jobName $jobName -jobDesc $jobDesc -jobCategory $jobCategory -jobAlertOperator $jobAlertOperator -jobEmailLevel $jobEmailLevel
#Create the SQL Job Step
$jobStep = CreateSqlJobStep $job $stepName $stepCmd
#Alter the Job to tell it what step should execute first
$job.StartStepID = $jobStep.ID
$job.Alter()
#Create the SQL Job Schedule
$CreateSqlJobSchedule = CreateSqlJobSchedule $job `
$schedName `
$schedFreqType `
$schedFreqRecurFactor `
$schedFreqInterval `
$schedFreqSubDayType `
$schedFreqSubDayInterval `
$startingSchedHour `
$startingSchedMinute `
$endingSchedHour `
$endingSchedMinute
$jobDetails = #{
CreateSqlJob = $CreateSqlJob
CreateSqlJobSchedule = $CreateSqlJobSchedule
}
return $jobDetails
}

One option you have is within the CreateSQLJob function to return a custom object that includes the pertinent information you want.
For instance:
$job = New-Object Object
$obj | Add-Member Noteproperty serverInstance -value $serverInstance
$obj | Add-Member Noteproperty jobName -value $jobName
Then you would be able to call into those properties:
$obj.jobName
The above is a contrived example.

Related

PowerShell - PSObject properties becoming sets with the name of the next property appended to the collection

I am trying to create a simple object in PowerShell that has four values. A boolean, two strings, and an integer. The problem is that if I call this function:
function New-ResultObject($Status, $Function, $Message, $Priority)
{
return New-Object psobject -property #{
Status = $Status; `
Function = $Function; `
Message = $Message; `
Priority = $Priority
}
}
Called this way:
$object= New-ResultObject -Status $false,`
-Function "FunctionName",`
-Message "This is the message",`
-Priority 2
Then do a $object | select * I get this as a result:
$object.Function = {FunctionName, -Status}
$object.Status = {False, -Function}
$object.Priority = 2
$object.Message = {This is the message, -Priority}
Instead of what I expected to be:
$object.Function = FunctionName
$object.Status = False
$object.Priority = 2
$object.Message = This is the message
I have tried doing an Add-Member approach with each property on its' own line, but it still results in the same object, where the property name is added as a second value to the initial property. What am I missing?
It's a common pitfall. You define the parameters as a comma delimited list but you call them space delimited.
$object= New-ResultObject -Status $false -Function "FunctionName" -Message "This is the message" -Priority 2
Also, I must implore you to stop using the backtick newline escaping trick. Instead you can use splatting.
$params = #{
Status = $false
Function = "FunctionName"
Message = "This is the message"
Priority = 2
}
$object = New-ResultObject #params
Thank you Doug Maurer. If you want to post an answer I can accept it. Otherwise, this was the issue, commas are not needed for multiple parameters and were causing the parameter tags to be passed in as multiple parts to the loosely-typed function parameters.
Here is a working example:
$object= New-ResultObject -Status $false `
-Function "FunctionName" `
-Message "This is the message" `
-Priority 2

AzureSQL Multiple Invoke-SQLCmd Loop and Connection Errors

I am attempting to loop through an invoke-sqlcmd for multiple AzureSQL databases via Azure Automation. The first item in the loop executes, but the all the rest fail with a:
Invoke-Sqlcmd : A network-related or instance-specific error occurred
while establishing a connection to SQL Server. The server was not
found or was not accessible. Verify that the instance name is correct
and that SQL Server is configured to allow remote connections.
(provider: Named Pipes Provider, error: 40 - Could not open a
connection to SQL Server)
I am guessing that I need to close the connection from the first invoke-sqlcmd before executing the next, but have not found a direct method to accomplish that with invoke-sqlcmd. Here is my loop:
param(
# Parameters to Pass to PowerShell Scripts
[parameter(Mandatory=$true)][String] $azureSQLServerName = "myazuresql",
[parameter(Mandatory=$true)][String] $azureSQLCred = "myazureautosqlcred"
)
# DB Name Array
$dbnamearray = #("database1","database2","database3")
$dbnamearray
# Datatable Name
$tabName = "RunbookTable"
#Create Table object
$table = New-Object system.Data.DataTable "$tabName"
#Define Columns
$col1 = New-Object system.Data.DataColumn dbname,([string])
#Add the Columns
$table.columns.add($col1)
# Add Row and Values for dname Column
ForEach ($db in $dbnamearray)
{
$row = $table.NewRow()
$row.dbname = $db
#Add the row to the table
$table.Rows.Add($row)
}
#Display the table
$table | format-table -AutoSize
# Loop through the datatable using the values per column
$table | ForEach-Object {
# Set loop variables as these are easier to pass then $_.
$azureSQLDatabaseName = $_.dbname
# Execute SQL Query Against Azure SQL
$azureSQLServerName = $azureSQLServerName + ".database.windows.net"
$Cred = Get-AutomationPSCredential -Name $azureSQLCred
$SQLOutput = $(Invoke-Sqlcmd -ServerInstance $azureSQLServerName -Username $Cred.UserName -Password $Cred.GetNetworkCredential().Password -Database $azureSQLDatabaseName -Query "SELECT * FROM INFORMATION_SCHEMA.TABLES " -QueryTimeout 65535 -ConnectionTimeout 60 -Verbose) 4>&1
Write-Output $SQLOutput
}
You can try making each connection as a powershell job. This solved a very similar issue I had some time ago. Send-MailMessage closes every 2nd connection when using attachments If you want to read an explanation. Basically, if you're unable to use a .Close() method, you can force connections to close by terminating the entire session for each run. In an ideal world the cmdlet would handle all this for you, but not everything was created perfectly.
# Loop through the datatable using the values per column
$table | ForEach-Object {
# Set loop variables as these are easier to pass then $_.
$azureSQLDatabaseName = $_.dbname
# Execute SQL Query Against Azure SQL
$azureSQLServerName = $azureSQLServerName + ".database.windows.net"
$Cred = Get-AutomationPSCredential -Name $azureSQLCred
# Pass in the needed parameters via -ArgumentList and start the job.
Start-Job -ScriptBlock { Write-Output $(Invoke-Sqlcmd -ServerInstance $args[0] -Username $args[1].UserName -Password $args[1].GetNetworkCredential().Password -Database $args[0] -Query "SELECT * FROM INFORMATION_SCHEMA.TABLES " -QueryTimeout 65535 -ConnectionTimeout 60 -Verbose) 4>&1 } -ArgumentList $azureSQLServerName, $Cred | Wait-Job | Receive-Job
}
This is untested since I do not have a server to connect to, but perhaps with a bit of work you can make something out of it.
I faced the same issue previously while doing something with the database of azure sql. You can try this
1. Create Automation Account
New-AzureRmAutomationAccount -ResourceGroupName $resourceGroupName -Name $automationAccountName -Location $location
2. Set the Automation account to work with
Set-AzureRmAutomationAccount -Name $automationAccountName -ResourceGroupName $resourceGroupName
3. Create / Import a Runbook
Here we already have a runbook ready so we import it. Here's the runbook code
workflow runbookNameValue
{
inlinescript
{
$MasterDatabaseConnection = New-Object System.Data.SqlClient.SqlConnection
$MasterDatabaseConnection.ConnectionString = "ConnectionStringValue"
# Open connection to Master DB
$MasterDatabaseConnection.Open()
# Create command
$MasterDatabaseCommand = New-Object System.Data.SqlClient.SqlCommand
$MasterDatabaseCommand.Connection = $MasterDatabaseConnection
$MasterDatabaseCommand.CommandText = "Exec stored procedure"
# Execute the query
$MasterDatabaseCommand.ExecuteNonQuery()
# Close connection to Master DB
$MasterDatabaseConnection.Close()
}
}
4. Importing
Import-AzureRMAutomationRunbook -Name $runBookName -Path $scriptPath -ResourceGroupName $resourceGroupName -AutomationAccountName $automationAccountName -Type PowerShell
I hope this helps. Instead of using Invoke-Sqlcmd use the $MasterDatabaseCommand.ExecuteNonQuery() like i've provided in the runbook. It will work
It seems that you append .database.windows.net to the server name inside the loop. I guess that's why it works for the first iteration only.
Just move this line:
$azureSQLServerName = $azureSQLServerName + ".database.windows.net"
before this line:
$table | ForEach-Object {

Return Object Array from Invoke-Command

Powershell script is only returning one value from Invoke-Command not all the objects I would expect.
I am adding a property to an existing object on a remote server then returning it back.
$inventory contains application objects that look like this:
Name : Centinel-Dev-AmazonServer
Platform : Centinel
Type : Windows Service
Tier : APP
Status : Running
Name : Portal-QA-Walmart
Platform : Portal
Type : Windows Service
Tier : APP
Status : Running
There are many more application objects similiar to the two given when I run my script it only returns the first object not all.
function verify_workingDir {
param($server)
$inventory = GetInventory -Server $server
$return_object = #()
$return_object += Invoke-Command -ComputerName $server -ScriptBlock {
$inventory = $args[0]
$return_object = #()
foreach ($Application in $inventory) {
$applicationParamters = Get-ItemProperty -Path x:\x\x\x\$($Application.Name)\Parameters
$verify_object = [pscustomobject] #{
WorkingDirectory = $applicationParamters.ServiceWorkingDir
}
$ExpandVerifyObject = $verify_object | Select-Object -Property #{
Name = "MyProperties"
Expression = {$_.WorkingDirectory }
} | Select-Object -ExpandProperty MyProperties
Add-Member -MemberType NoteProperty -Name WorkingDirectory -InputObject $Application -TypeName PSObject -Value $ExpandVerifyObject
$return_object += $Application
}
return $return_object
} -ArgumentList ($inventory) #| Select Name, Platform, Type, Tier, Status, ServerName, WorkingDirectory
return $return_object
}
Answer:
$inventory = $args[0] ---> $inventory = $args
Try $inventory = $args instead of $inventory = $args[0].
Passing arrays to script blocks is not the easiest thing because $args flattens everything to a single array. If you pass -ArgumentList #(1,2,3), 4, then $Args will be a single array #(1,2,3,4) and $Args[0] will be 1.
If you ever need to pass complex arguments with -ArgumentList, pass them all as a single HashTable: -ArgumentList #{FirstArg = #(1,2,3); SecondArg = 4}.

PowerShell - Lambda Functions Not Executing

This lambda function executes as expected:
$WriteServerName = {
param($server)
Write-Host $server
}
$server = "servername"
$WriteServerName.invoke($server)
servername
However, using the same syntax, the following script prompts for credentials and then exits to the command line (running like this: .\ScriptName.ps1 -ConfigFile Chef.config), implying that the lambda functions aren't executing properly (for testing, each should just output the server name).
Why does the former lambda function return the server name, but the ones in the script don't?
Param(
$ConfigFile
)
Function Main {
#Pre-reqs: get credential, load config from file, and define lambda functions.
$jobs = #()
$Credential = Get-Credential
$Username = $Credential.username
$ConvertedPassword = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.password)
$Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($ConvertedPassword)
$Config = Get-Content $ConfigFile -Raw | Out-String | Invoke-Expression
#Define lambda functions
$BootStrap = {
param($Server)
write-host $server
}
$RunChefClient = {
param($Server)
write-host $server
}
$SetEnvironment = {
param($Server)
write-host $server
}
#Create bootstrap job for each server and pass lambda functions to Scriptblock for execution.
if(($Username -ne $null) -and ($Password -ne $null))
{
ForEach($HashTable in $Config)
{
$Server = $HashTable.Server
$Roles = $HashTable.Roles
$Tags = $HashTable.Tags
$Environment = $HashTable.Environment
$ScriptBlock = {
param ($Server,$BootStrap,$RunChefClient,$SetEnvironment)
$BootStrap.invoke($Server)
$RunChefClient.invoke($Server)
$SetEnvironment.invoke($Server)
}
$Jobs += Start-Job -ScriptBlock $ScriptBlock -ArgumentList #($Server,$BootStrap,$RunChefClient,$SetEnvironment)
}
}
else {Write-Host "Username or password is missing, exiting..." -ForegroundColor Red; exit}
}
Main
Without testing, I am going to go ahead and say it's because you are putting your scriptblock executions in PowerShell Jobs and then not doing anything with them. When you start a job, it starts a new PowerShell instance and executes the code you give it along with the parameters you give it. Once it completes, the completed PSRemotingJob object sits there and does nothing until you actually do something with it.
In your code, all the jobs you start are assigned to the $Jobs variable. You can also get all your running jobs with Get-Job:
Get-Job -State Running
If you want to get any of the data returned from your jobs, you'll have to use Receive-Job
# Either
$Jobs | Receive-Job
# Or
Get-Job -State Running | Receive-Job

What is the cast type for System.DirectoryServices.DirectorySearcher.FindAll()?

As I go through the Properties values in System.DirectoryServices.DirectorySearcher.FindAll() and initialize it as a new-object to a variable, I noticed after after 50,000 or more my memory starts getting real bad.
To counter this, I am assigning it as a process, but I want to do it in increments of 10,000. To do this, I need to know how to pass the $list as an argument, so I need to know it's cast type...
Note: According to https://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.findall(v=vs.110).aspx the type is A SearchResultCollection object.
Example code:
$domain = "LDAP://some.example.com"
[string[]]$properties = "company" ,"sAMAccountName", "displayName", "name"
$dn = New-Object System.DirectoryServices.DirectoryEntry($domain)
$ds = New-Object System.DirectoryServices.DirectorySearcher($dn)
$ds.Filter = '(&(objectCategory=person)(objectClass=user))'
$ds.PropertiesToLoad.AddRange($properties)
[?]$list = $ds.FindAll()
Powershell -command {
Param ([string[]]$properties, [?]$list)
#do stuff with $list
} -args $properties $list
what would the [?] be in the [?]$list = $ds.FindAll() for powershell?
If you are getting effected with performance, I recommend creating jobs.
here I am doing it for 1000 objects
it'll go from 0 to 1000
1001 to 2000 and so on .
$total = $list.Count
for($($Count=0;$r=0;$t=1000);$Count -lt ($total/1000 -as [int]) ; $($r=$t+1;$T+=1000;$Count++))
{
Start-Job -ScriptBlock {param($list)
#do stuff here
} -ArgumentList $list[$r..$t]
}
this will output details of each job,
Do Receive-Job to get the output for each job.
more about_Jobs here https://technet.microsoft.com/en-us/library/hh847783.aspx
best regards,
kvprasoon