I have an issue with powershell scriptblock executed by ServiceNow MID Server on target host.
Target host is running SQL server where I need to execute some PS Script running SQL commands and post process results (using JDBC activity is not valid in this case).
So I am running powershell on MID server (some kind of proxy, for these who are not familiar with ServiceNow) and I am needed to execute PS script using Encoded command via Invoke-WMIMethod cmdlet (I cannot use Invoke-Command, due to disabled PS remoting - no way to enable that due to company policies) like below:
$Bytes=[System.Text.Encoding]::Unicode.GetBytes($CallArgumentsBlock)
# Command block is passed as encoded command
$EncodedCommand=[Convert]::ToBase64String($Bytes)
$WMIArgs=#{
Class='win32_process'
Name='Create'
ComputerName=$computer
ArgumentList="powershell -EncodedCommand $EncodedCommand"
ErrorAction='SilentlyContinue'
Credential=$cred
}
Invoke-WmiMethod #WMIArgs | Out-Null
Issue is that I need to push into $EncodedCommand pre-evaluated variables. Example of script block:
$CallArgumentsBlock={
Param(
[string] $SQLServer = "$inputSQLServer", #SQL Server Name running on host
[string] $SQLDBName = "$inputSQLDBName", #SQL DB name
[string] $InputData = "$inputInputData", #JSON string holding data processed by PS+SQL
[string] $ResultFolderPath = "$OutputPath_forSQL" #File path where to output results cause WMI can not return output of invoked command
)
$ProcessData = ConvertFrom-JSON -InputObject $InputData
#For each object call sql...
...
*PS code*
...
}
So what should be powershell way to do it?
Does anybody suggest better ServiceNow based solution?
Thanks a lot for your answers!
My initial thought is to remove the Param() statement, and just list the variables and set the values when creating the script block, any reason this wouldn't work?
$CallArgumentsBlock={
[string] $SQLServer = "$inputSQLServer" #SQL Server Name running on host
[string] $SQLDBName = "$inputSQLDBName" #SQL DB name
[string] $InputData = "$inputInputData" #JSON string holding data processed by PS+SQL
[string] $ResultFolderPath = "$OutputPath_forSQL" #File path where to output results cause WMI can not return output of invoked command
$ProcessData = ConvertFrom-JSON -InputObject $InputData
#For each object call sql...
...
*PS code*
...
}
Related
I am trying to write a powershell script that opens a remote desktop connection for each machine name saved in a text file. When I run the script, it only connects to the first machine in the list and outputs to the console: CMDKEY: Credential added successfully once (not once for each machine). mstcs seems to terminate the process after executing, and I'm not sure I'm adding credentials the right way. Can anyone point me in the right direction?
Here are some tests I've tried to figure out what's going on:
Print after mstsc. Doesn't print. Process seems to terminate after
mstcs is called. This seems to be the crux of the issue.
cmdkey /list shows all the credentials I have stored and their targets. The output does not include all the targets defined in the text file. Even if I comment out mstsc, then cmdkey /add:$MachineName /user:$User /pass:$Password only seems to execute for the first line, evidenced by the lack of more console outputs and cmdkey /list not yielding the expected targets. In addition, I have added a print statement after this cmdkey line and it prints for each line, so it doesn't terminate after running (which I already knew because mstcs executes after this line when it's not commented out).
# Read from file
$Lines = Get-Content -Path .\machines.txt | Out-String
# For each machine ...
foreach($Line in $Lines){
# Split line, save name and domain
$Tokens = $Line.Split(".")
$MachineName = $Tokens[0]
$Domain = $Tokens[1]
$User = "someDomain\someUsername"
$Password="somePassword"
# Switch username if someOtherDomain
if ($Domain -eq "someOtherDomain"){
$User = "someOtherDomain\someOtherUsername"
}
#set credentials and open connection
cmdkey /add:$MachineName /user:$User /pass:$Password
mstsc /v:$MachineName /console
}
EDIT: I have also tried replacing mstsc /v:$MachineName with Start-Process -FilePath "$env:windir\system32\mstsc.exe" -ArgumentList "/v:$MachineName" -Wait. The result is opening the session and then the script does not finish in the console but nothing additional happens.
This behavior is cause by your use of Out-String.
Get-Content outputs multiple strings, one per line in the file - but Out-String stitches them back together into a single multi-line string:
PS C:\> $machines = Get-Content machines.txt
PS C:\> $machines.GetType().Name # definitely an array
Object[]
PS C:\> $machines.Count # multiple strings in there
4
PS C:\> $machines = Get-Content machines.txt | Out-String
PS C:\> $machines.GetType().Name # now it's just a single string
String
So your foreach(){} loop only runs once, and the value of $MachineName is no longer the name of a single machine, but a multi-line string with all of them at once - which is probably why mstsc exits immediately :)
Remove |Out-String from the first line and your loop will work
I am trying to run a PowerShell script daily through task scheduler, however the script will not run. When I enter the code below manually into PowerShell (as an administrator), it makes me press enter twice. I believe since i have to press enter twice is the reason it will not run through the task scheduler.
Is there a way to adjust my code to get this to work with the task scheduler?
I am running Windows 2012 R2 and Version 5.1 of PowerShell.
Please note that i ran the exact same script on my computer, which is Windows 10 and running version 5.1 of PowerShell, and it worked the correct way (only had to press enter once)
I expect to only press enter once to run my PowerShell script, but the actual output from the first time i press enter brings another line with just ">>" and then i press enter the second time and the script executes.
Powershell Script:
# Load WinSCP .NET assembly
Add-Type -Path "WinSCPnet.dll"
# Set up session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Sftp
HostName = ""
UserName = ""
Password = ""
SshHostKeyFingerprint = ""
}
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
# Transfer files
$session.PutFiles("", "").Check()
}
finally
{
$session.Dispose()
}
If a script requires user interaction, then it really should not be a scheduled task.
If you write a script that requires confirmation, then you need to look using -Confirm parameter.
See Are you sure? Using the -WhatIf and -Confirm parameters in PowerShel
Remove-MailContact -Identity “$sourceEmail” -Confirm:$Y -WhatIf
The cmdlet or code you write has to support it. For code you write, that means using advanced functions.
How to write a PowerShell function to use Confirm, Verbose and WhatIf
function Set-FileContent
{
[cmdletbinding(SupportsShouldProcess)]
Param
(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Content,
[Parameter(Mandatory = $true)]
[ValidateScript( {Test-Path $_ })]
[string]$File
)
if ($PSCmdlet.ShouldProcess("$File" , "Adding $Content to "))
{
Set-Content -Path $File -Value $Content
}
}
See also ConfirmPreference
I have a powershell script that provides SQL Agent Job information. The script is meant to monitor the jobs and tell me if they failed the last time they ran. However i also want to know which was the last run step. and i can't seem to figure out how.
I am querying the Sql server mangement object, because it needs to be usable on multiple remote servers (remote connections) and i wish to avoid running SQL scripts.
Keep in mind i'm rather new to powershell.
This is the code i have so far: And i have loaded the SMO libraries, i just didn't show it the copied script.
## Get Jobstep Class SMO
Push-Location; Import-Module SQLPS -DisableNameChecking; Pop-Location;
$JobStep = New-Object microsoft.sqlserver.management.smo.agent.jobstep
## Run the select from the local SQL server management objects
$SQLSvr = "."
$MySQLObject = new-object Microsoft.SqlServer.Management.Smo.Server `
$SQLSvr;;
$Select = ($MySQLObject.JobServer.jobs) `
| Select Name, isEnabled, `
lastRunDate, lastRunOutCome, NextRunDate `
| Where-Object {$_.LastRunDate -ge ((Get-Date).adddays(-2)) } `
| ft -AutoSize;
$Select
The push-location section is meant to get the correct smo class for selecting the job step (unsure if it is right), however i haven't added anything from it.
Now the script does work, i get no errors and i get returned the overall information i want, but i cannot figure out how to add the jobstep - and i have consulted google. I'm well aware that i need to add more to the select, but what is the issue for me. So, how do i extract the last run job step from SQL Agent job, using the SMO, and add it to the above script?
You can use the SqlServer module from the PowerShell Gallery (Install-Module SqlServer), and then something like:
$h = Get-SqlAgentJobHistory -ServerInstance servername -JobName jobname
$h[0] will give you the last step ran.
This will give you the result in the format you wanted:
Get-SqlAgentJob -ServerInstance servername |
Where-Object {$_.LastRunDate -ge ((Get-Date).AddDays(-2))} | ForEach-Object {
$h = Get-SqlAgentJobHistory -ServerInstance servername -JobName $_.Name
[PSCustomObject]#{
Name = $_.Name
IsEnabled = $_.IsEnabled
LastRunDate = $_.LastRunDate
LastRunOutcome = $_.LastRunOutcome
NextRunDate = $_.NextRunDate
LastRunStep = $h[0].StepName
}
}
I'm attempting to run a PowerShell script with the input being the results of another PowerShell cmdlet. Here's the cross-forest Exchange 2013 PowerShell command I can run successfully for one user by specifying the -Identity parameter:
.\Prepare-MoveRequest.ps1 -Identity "user#domain.com" -RemoteForestDomainController "dc.remotedomain.com" $Remote -UseLocalObject -OverwriteLocalObject -Verbose
I want to run this command for all MailUsers. Therefore, what I want to run is:
Get-MailUser | select windowsemailaddress | .\Prepare-MoveRequest.ps1 -RemoteForestDomainController "dc.remotedomain.com" $Remote -LocalForestDomainController "dc.localdomain.com" -UseLocalObject -OverwriteLocalObject -Verbose
Note that I removed the -Identity parameter because I was feeding it from each Get-MailUser's WindowsEmailAddress property value. However, this returns with a pipeline input error.
I also tried exporting the WindowsEmailAddress property values to a CSV, and then reading it as per the following site, but I also got a pipeline problem: http://technet.microsoft.com/en-us/library/ee861103(v=exchg.150).aspx
Import-Csv mailusers.csv | Prepare-MoveRequest.ps1 -RemoteForestDomainController DC.remotedomain.com -RemoteForestCredential $Remote
What is the best way to feed the windowsemailaddress field from each MailUser to my Prepare-MoveRequest.ps1 script?
EDIT: I may have just figured it out with the following foreach addition to my Import-Csv option above. I'm testing it now:
Import-Csv mailusers.csv | foreach { Prepare-MoveRequest.ps1 -Identity $_.windowsemailaddress -RemoteForestDomainController DC.remotedomain.com -RemoteForestCredential $Remote }
You should declare your custom function called Prepare-MoveRequest instead of simply making it a script. Then, dot-source the script that declares the function, and then call the function. To accept pipeline input into your function, you need to declare one or more parameters that use the appropriate parameter attributes, such as ValueFromPipeline or ValueFromPipelineByPropertyName. Here is the official MSDN documentation for parameter attributes.
For example, let's say I was developing a custom Stop-Process cmdlet. I want to stop a process based on the ProcessID (or PID) of a Windows process. Here is what the command would look like:
function Stop-CustomProcess {
# Specify the CmdletBinding() attribute for our
# custom advanced function.
[CmdletBinding()]
# Specify the PARAM block, and declare the parameter
# that accepts pipeline input
param (
[Parameter(ValueFromPipelineByPropertyName = $true)]
[int] $Id
)
# You must specify the PROCESS block, because we want this
# code to execute FOR EACH process that is piped into the
# cmdlet. If we do not specify the PROCESS block, then the
# END block is used by default, which only would run once.
process {
Write-Verbose -Message ('Stopping process with PID: {0}' -f $ID);
# Stop the process here
}
}
# 1. Launch three (3) instances of notepad
1..3 | % { notepad; };
# 2. Call the Stop-CustomProcess cmdlet, using pipeline input
Get-Process notepad | Stop-CustomProcess -Verbose;
# 3. Do an actual clean-up
Get-Process notepad | Stop-Process;
Now that we've taken a look at an example of building the custom function ... once you've defined your custom function in your script file, dot-source it in your "main" script.
# Import the custom function into the current session
. $PSScriptRoot\Prepare-MoveRequest.ps1
# Call the function
Get-MailUser | Prepare-MoveRequest -RemoteForestDomainController dc.remotedomain.com $Remote -LocalForestDomainController dc.localdomain.com -UseLocalObject -OverwriteLocalObject -Verbose;
# Note: Since you've defined a parameter named `-WindowsEmailAddress` that uses the `ValueFromPipelineByPropertyName` attribute, the value of each object will be bound to the parameter, as it passes through the `PROCESS` block.
EDIT: I would like to point out that your edit to your post does not properly handle parameter binding in PowerShell. It may achieve the desired results, but it does not teach the correct method of binding parameters in PowerShell. You don't have to use the ForEach-Object to achieve your desired results. Read through my post, and I believe you will increase your understanding of parameter binding.
My foreach loop did the trick.
Import-Csv mailusers.csv | foreach { Prepare-MoveRequest.ps1 -Identity $_.windowsemailaddress -RemoteForestDomainController DC.remotedomain.com -RemoteForestCredential $Remote }
I am trying to run queries stored in a text file from PowerShell. I use following to do that;
Invoke-Expression 'sqlcmd -d TestDB -U $user -P $pw -i "E:\SQLQuery1.sql"'
If an error or exception occurs when executing the queries from the .sql file, how can I capture that in my Powershell script? How can I get the script output?
NOTE: I cannot use invoke-sqlcmd
To answer the question
If some error or exception occurred when executing .sql file how can I get that into my PowerShell script? How can I get the script output?"
Invoke-Expression returns the output of the expression executed. However, it may only capture STDOUT, not STDERR (I haven't tested, as I don't use this method), so you might not get the actual error message.
From the Help:
The Invoke-Expression cmdlet evaluates or runs a specified string as a command and returns the results of the expression or command
The better route is to use the PowerShell method you already have available - Invoke-SQLCmd is installed if you have installed any of the SQL Server 2008 (or newer) components/tools (like SSMS). If you've got SQL Server 2012, it's very easy: import-module sqlps. For 2008, you need to add a Snap-In, add-pssnapin SqlServerCmdletSnapin. And since you have sqlcmd.exe, the PowerShell components should be there already.
If all else fails, go the System.Data.SQLClient route:
$Conn=New-Object System.Data.SQLClient.SQLConnection "Server=YOURSERVER;Database=TestDB;User Id=$user;password=$pw";
$Conn.Open();
$DataCmd = New-Object System.Data.SqlClient.SqlCommand;
$MyQuery = get-content "e:\SQLQuery1.sql";
$DataCmd.CommandText = $MyQuery;
$DataCmd.Connection = $Conn;
$DAadapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$DAadapter.SelectCommand = $DataCmd;
$DTable = New-Object System.Data.DataTable;
$DAadapter.Fill($DTable)|Out-Null;
$Conn.Close();
$Conn.Dispose();
$DTable;
With both this and Invoke-SQLCmd, you'll be able to use try/catch to pick up and handle any error that occurs.
As seen in this question's answers, there is a method built into Powershell to invoke SQLCMD called, unsurprisingly, Invoke-Sqlcmd.
It's very easy to use for individual files:
Invoke-sqlcmd -ServerInstance $server -Database $db -InputFile $filename
Or groups:
$listOfFiles | % { Invoke-sqlcmd -ServerInstance $server -Database $db -InputFile $_ }
Use invoke-sqlquery module, available at this website.