Write-Output inside Invoke-Command scriptblock - powershell

Below is a script I'm working on to get all SQL-jobs into a CSV-file.
The script itself is working great but I have trouble with the error-handling.
I can't figure out how to get the Out-File inside the Catch-block to print to the file on my local machine instead of the remote machine I'm running the Invoke-Command to.
How do I accomplish this?
Thanks
PS. The script is written out fully as much as possible for non experienced co-workers convenience
$sqlServers = #("TEST1","TEST2")
$filePath = [Environment]::GetFolderPath("Desktop")
$dateToday = Get-Date -Format “yyMMdd HH:mm"
$dateTodayFile = Get-Date -Format “yyMMdd"
Write-Output "$dateToday $sqlServers" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
$output = Invoke-Command -ComputerName $sqlServers -ScriptBlock{
Try
{
Import-Module sqlserver -ErrorAction Stop
}
Catch
{
Write-Output "$dateToday ERROR $env:computername" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Exit
}
$instances = $env:computername | Foreach-Object {Get-ChildItem -Path "SQLSERVER:\SQL\$_"}
ForEach ($instance in $instances){
Try
{
$instanceName = $instance.InstanceName
Get-SqlAgentJob -ServerInstance "$env:computername\$instanceName" -ErrorAction Stop |
Where-Object {$_.IsEnabled -eq "True" -and $_.LastRunDate -gt [DateTime]::Today.AddDays(-2) -and $_.OwnerLoginName -match "TEST"} |
Select-Object #{Name=‘Job name‘;Expression={$_.Name}},
#{Name=‘Description‘;Expression={$_.Description}},
#{Name=‘Instance‘;Expression={$_.Parent -Replace '[][]'}},
#{Name=‘Run outcome‘;Expression={$_.LastRunOutcome}},
#{Name=‘Run date‘;Expression={$_.LastRunDate}},
#{Name=‘Run duration‘;Expression={$_.LastRunDuration}},
#{Name=‘Job creator‘;Expression={$_.OwnerLoginName}},
#{Name=‘Runs on a schedule‘;Expression={$_.HasSchedule}},
#{Name='Schedule Type';Expression={$_.JobSchedules -join ','}}
}
Catch
{
Write-Output "$dateToday ERROR $env:computername\$instanceName" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Exit
}
}
}
$output | Select-Object -Property * -ExcludeProperty PSComputerName,RunSpaceID,PSShowComputerName |
Sort-Object "Job name" |
Export-Csv $filePath\SQLJobInvent$dateTodayFile.csv -NoTypeInformation -Delimiter ";" -Encoding UTF8
Write-Output "$dateToday $filePath" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Write-Output "----------------------------------------" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append

Your primary issue is scope.
The $dateToday, $filePath and $dateTodayFile are all declared on the local machine, but you're trying to use them on the remote computer (script block) where they are undefined.
There are a few ways to get your variables passed to the remote computer, below are two:
# Add desired variable to ArgumentList and define it as a parameter
Invoke-Command -ComputerName $sqlServers -ArgumentList $dateToday,$filePath,$dateTodayFile -ScriptBlock {
param(
$folderPath,
$filePath,
$dateTodayFile
)
# Do something with our injected variables
Write-Output "$dateToday ERROR $env:computername" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
}
OR
# In PS ver >= 3.0 we can use 'using'
Invoke-Command -ComputerName $serverName -ScriptBlock {Write-Output $using:dateToday}

Related

Send variables from inside Invoke-Command scriptblock back to host

The below is a script that is collecting information about SQL-jobs on remote servers.
However, I want to send the information inside the catch-block back to the host.
As the script is written now the script is printing to a logfile on each remote server.
How can I send the information back to the host?
$sqlServers = #("SERVER1","SERVER2")
$runningHost = "$env:computername"
$filePath = "C:\SQLJobInventory"
$desktopPath = [Environment]::GetFolderPath("Desktop")
$output = Invoke-Command -ComputerName $sqlServers -ArgumentList $filePath,$dateToday,$dateTodayFile -ScriptBlock{
param
(
$filePath,
$dateToday,
$dateTodayFile
)
$runningHostRemote = $env:computername
Try
{
Import-Module sqlserver -ErrorAction Stop
$instances = $runningHostRemote | Foreach-Object {Get-ChildItem -Path "SQLSERVER:\SQL\$_"} -ErrorAction Stop
}
Catch
{
Write-Output "$dateToday [ERROR] $runningHostRemote" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Exit
}
ForEach ($instance in $instances)
{
Try
{
$instanceName = $instance.InstanceName
Get-SqlAgentJob -ServerInstance "$runningHostRemote\$instanceName" -ErrorAction Stop |
Where-Object {$_.IsEnabled -eq "True" -and $_.LastRunDate -gt [DateTime]::Today.AddDays(-2) -and $_.OwnerLoginName -match "LKL"} |
Select-Object #{Name=‘Job name‘;Expression={$_.Name}},
#{Name=‘Description‘;Expression={$_.Description}},
#{Name=‘Instance‘;Expression={$_.Parent -Replace '[][]'}},
#{Name=‘Run outcome‘;Expression={$_.LastRunOutcome}},
#{Name=‘Run date‘;Expression={$_.LastRunDate}},
#{Name=‘Run duration‘;Expression={$_.LastRunDuration}},
#{Name=‘Job creator‘;Expression={$_.OwnerLoginName}},
#{Name=‘Runs on a schedule‘;Expression={$_.HasSchedule}},
#{Name='Schedule Type';Expression={$_.JobSchedules -join ','}}
}
Catch
{
Write-Output "$dateToday [ERROR] $runningHostRemote\$instanceName" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Exit
}
}
}
$output | Select-Object -Property * -ExcludeProperty PSComputerName,RunSpaceID,PSShowComputerName |
Sort-Object "Job name" |
Export-Csv $filePath\SQLJobInvent$dateTodayFile.csv -NoTypeInformation -Delimiter ";" -Encoding UTF8
Write-Output "$dateToday [INFO] $filePath\Log$dateTodayFile.txt" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Change write-output to return
Catch
{
Return "$dateToday [ERROR] $runningHostRemote\$instanceName"
}
Return will exit the script block and pass your string back to the output variable.
I have solved it by creating my own properties of the output-variable with New-Object.
There is probably a better way to do it but this was the most convinient.
The Return-method did not work for me in this particular script.
$runningHost = "$env:computername"
$filePath = "C:\SQLJobInventory"
$lastResortPath = [Environment]::GetFolderPath("Desktop")
$dateToday = Get-Date -Format “yyMMdd HH:mm"
$dateTodayFile = Get-Date -Format “yyMMdd"
$output = Invoke-Command -ComputerName $sqlServers -ArgumentList $filePath,$dateToday,$dateTodayFile -ScriptBlock{
param
(
$filePath,
$dateToday,
$dateTodayFile
)
$runningHostRemote = $env:computername
Try
{
Import-Module sqlserver -ErrorAction Stop
$instances = $runningHostRemote | Foreach-Object {Get-ChildItem -Path "SQLSERVER:\SQL\$_"} -ErrorAction Stop
}
Catch
{
$moduleError = #{moduleError="$dateToday [ERROR] $runningHostRemote"}
New-Object -Type PSObject -Property $moduleError
Exit
}
ForEach ($instance in $instances){
Try
{
$instanceName = $instance.InstanceName
$jobSuccess = #{jobSuccess="$dateToday [INFO]"}
New-Object -Type PSObject -Property $jobSuccess
Get-SqlAgentJob -ServerInstance "$runningHostRemote\$instanceName" -ErrorAction Stop |
Where-Object {$_.IsEnabled -eq "True" -and $_.LastRunDate -gt [DateTime]::Today.AddDays(-2) -and $_.OwnerLoginName -match "LKL"} |
Select-Object #{Name=‘Job name‘;Expression={$_.Name}},
#{Name=‘Description‘;Expression={$_.Description}},
#{Name=‘Instance‘;Expression={$_.Parent -Replace '[][]'}},
#{Name=‘Run outcome‘;Expression={$_.LastRunOutcome}},
#{Name=‘Run date‘;Expression={$_.LastRunDate}},
#{Name=‘Run duration‘;Expression={$_.LastRunDuration}},
#{Name=‘Job creator‘;Expression={$_.OwnerLoginName}},
#{Name=‘Runs on a schedule‘;Expression={$_.HasSchedule}},
#{Name='Schedule Type';Expression={$_.JobSchedules -join ','}}
}
Catch
{
$jobError = #{jobError="$dateToday [ERROR] $runningHostRemote\$instanceName"}
New-Object -Type PSObject -Property $jobError
Exit
}
}
}
$output | Select-Object -ExpandProperty moduleError -ErrorAction SilentlyContinue | Out-File "$filePath\Log$dateTodayFile.txt" -Append
$output | Select-Object -ExpandProperty Jobsuccess -ErrorAction SilentlyContinue | Out-File "$filePath\Log$dateTodayFile.txt" -Append
$output | Select-Object -ExpandProperty jobError -ErrorAction SilentlyContinue | Out-File "$filePath\Log$dateTodayFile.txt" -Append
$output | Select-Object -Property * -ExcludeProperty PSComputerName,RunSpaceID,PSShowComputerName |
Sort-Object "Job name" |
Export-Csv $filePath\SQLJobInvent$dateTodayFile.csv -NoTypeInformation -Delimiter ";" -Encoding UTF8
Write-Output "$dateToday [INFO] $filePath\Log$dateTodayFile.txt" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append

Powershell output logging when using a text file to gather server names

Have a bit of an issue whereby would like to figure out the best way to handle success or failures. Have a powershell query which checks the dcom port range, if it is within the specified value output to a success file, if not a failure file. The issue is, it seems to be outputting the entire serverlist.txt for a success and need to know a way to break this down so it only appends a server (either success/failure) to it, not all at once.
Here is the powershell script contents:
powershell -executionpolicy bypass .\DCOMPortRange.ps1
Where DCOMPortRange.ps1 contains
$computername = Get-Content -Path "C:\Folderpath\serverlist.txt"
$val = (Get-ItemProperty "hklm:SOFTWARE\Microsoft\Rpc\Internet") | Select-Object -ExpandProperty Ports
if($val -eq "50000-50500")
{
Write-Output "$computername" | out-file C:\folderpath\Success.log -append
} Else {
Write-Output "$computername" | out-file C:\folderpath\Failure.log -append
}
The issue is the error path lets say is a success it appends the entire server list.
Please advise?
This is how I would do it. This does require that you do have PSremoting enabled on the servers
$computername = Get-Content -Path "C:\Folderpath\serverlist.txt"
ForEach ($server in $computername) {
$val = Invoke-Command -Computername $server -ScriptBlock {(Get-ItemProperty "hklm:SOFTWARE\Microsoft\Rpc\Internet") | Select-Object -ExpandProperty Ports}
if ($val -ge 50000 -and $val -le 50500) {
Write-Output "$server" | out-file C:\folderpath\Success.log -append
}
Else {
Write-Output "$server" | out-file C:\folderpath\Failure.log -append
}
}
Edit: A change to the if statement
/Anders
$remotecomputername = #("PC1","PC2","RealServerName")
ForEach ($computer in $remotecomputername) {
Invoke-Command -Computername $computer -ScriptBlock { $val = (Get-
ItemProperty "hklm:SOFTWARE\Microsoft\Rpc\Internet") | Select-Object -
ExpandProperty Ports} }
if($val -eq "50000-50500") {
write-host $computer DCOM Port in Range
} else {
write-host $computer DCOM Port not in range
}

Why PS didn't saved informations in these txt files?

I didn't got any errors and powershell made these files, but they are empty. What did i wrong?
$Services = Get-Service
Foreach ($Proces in $Services) {
If($Proces.status -eq "running") { Out-File $Proces >> "C:\proces.txt"}
If($Proces.status -eq "stopped") { Out-File $Proces >> "C:\proces2.txt"}
}
>> is the append redirect operator, which is basically the same as Out-File -Append. So it is like calling Out-File twice.
With the command Out-File $Proces >> "C:\proces.txt" you pass in no input object to Out-File. So you write a blank file to $Proces. Then take the output of that command (nothing) and write that to C:\proces.txt, which creates the second blank file.
So you will want to decide on using Out-File -Append or >>
Here is your code using just Out-File:
$Services = Get-Service
Foreach ($Service in $Services) {
If ($Service.Status -eq "Running") { Out-File -InputObject $Service -Path "C:\proces.txt" -Append}
If ($Service.Status -eq "Stopped") { Out-File -InputObject $Service -Path "C:\proces2.txt" -Append }
}
Here is your code using just >>:
$Services = Get-Service
Foreach ($Service in $Services) {
If ($Service.Status -eq "Running") { $Service >> "C:\proces.txt" }
If ($Service.Status -eq "Stopped") { $Service >> "C:\proces2.txt" }
}
There are many other ways to do what you are attempting
Here is a way using the Where-Object cmdlet rather than a loop/conditional
$Services = Get-Service
$Services | Where-Object {$_.Status -eq "Running"} | Out-File "C:\proces.txt" -Append
$Services | Where-Object {$_.Status -eq "Stopped"} | Out-File "C:\proces2.txt" -Append
Here is a way using the .where() method using split
$Running,$Stopped = (Get-Service).Where({$_.Status -eq 'Running'},'Split')
$Running | Out-File "C:\proces.txt" -Append
$Stopped | Out-File "C:\proces2.txt" -Append

How to Remote Select-String several servers with Powershell

The idea is to search for a pattern on several servers with Select-String and Invoke-Command.
I am not able to get the $results back to the local server correctly and print it either in a file and/or also in the console (this is not so important).
What I need is to be able to see the results of the search (filename, line, match)
Set Execution-Policy RemoteSigned
$servidores = Get-Content "C:\ServerList.txt"
(Get-Date).ToString()
Write-Output "----------------Running script------------------"
Write-Output "---------------Servers in scope :---------------"
Write-Output $servidores
Write-Output "-----------------Starting Loop-----------------"
$ToExecute = {
Select-String -SimpleMatch "password" -Path C:\*.* -Exclude C:\Users\Public\resultados.txt
}
foreach ($server in $servidores){
$result = Invoke-Command -ComputerName $server -ScriptBlock $ToExecute
Write-Output "----------Executing Search on Server:-----------"
(Get-Date).ToString();$server;
Write-Output "------------------------------------------------"
Write-Output $result
Out-File $result C:\Users\Public\resultados.txt
}
For Out-File if you are not piping, you will need to use the -inputobject flag. Because Out-File does not take the inputobject by position.
Out-File -InputObject $result -path C:\Users\Public\resultados.txt
Otherwise you could use Tee-Object to replace the write-output/outfile.
$result | Tee -filepath C:\Users\Public\resultados.txt
Set Execution-Policy RemoteSigned
$servidores = Get-Content "C:\ServerList.txt"
Write-Output ("----------------Running script-----"+(Get-Date).ToString()+"-- -----------")
Write-Output "--------------------------Servers in scope----------------------------"
Write-Output $servidores
foreach ($server in $servidores){
Write-Output ("---Executing Search on Server:---"+$server+"----at:"+(Get- Date).ToString()+"-------------")
$result= Invoke-Command -ComputerName $server -ScriptBlock {Select-String - Pattern "password" -Path C:\*.txt -AllMatches}
if ($result -ne $null){
Write-Output $result.ToString()
}
}Read-Host -Prompt "Press Enter to exit"

PS script errors in first run: The object of type ".PowerShell.Commands.Internal.Format.FormatStartData" is not valid or not in the correct sequence

I have the following function that works correctly in 2012r2, when i run it in 2008R2 it throws the below error. The surprising thing is that if i execute it a second time, it works without any issue!!
# Function Reg-Stamp {
Param(
[Parameter(Mandatory=$True)]
[string]$Phase
)
$msg = "`nEntering: $((Get-Variable MyInvocation -Scope 0).Value.MyCommand.Name)" ; Write-Host -fore Gray $msg ; $msg | out-file -Append $log
$RegStampInfo = Build-Variable $RegStampCSV
$Version = ($ScriptVersionInfo | Where-Object {$_.Parameter -eq "Version" -and $_.Phase -eq $Phase }).Value
$DisplayName = ($ScriptVersionInfo | Where-Object {$_.Parameter -eq "DisplayName" -and $_.Phase -eq $Phase }).Value
$BuildDate = ($ScriptVersionInfo | Where-Object {$_.Parameter -eq "BuildDate" -and $_.Phase -eq $Phase }).Value
$RunDate = Get-Date
$Success = $(-not($CriticalError))
$msg = "`nUpdating registry with build information"; Write-Host -fore Gray $msg; $msg | out-file $log -Append;
$RegStampInfo | Where-Object {($_.Phase.ToLower()) -eq ($Phase.ToLower())} | foreach-Object {
$ValueData = $(get-variable -Name $($_.StampData) -ValueOnly -ErrorAction SilentlyContinue)
$msg = "Adding Key: $($_.StampKey) '$($_.StampValue)' '$ValueData'"; Write-Host -fore Green "$msg"; $msg | out-file $log -Append;
New-Item -Path $($_.StampKey) -ErrorAction SilentlyContinue
Set-ItemProperty -Path $_.StampKey -name $_.StampValue -Value $ValueData
}
$msg = "`nExiting: $((Get-Variable MyInvocation -Scope 0).Value.MyCommand.Name)"; Write-Host -fore DarkGreen $msg ; $msg | out-file -Append $log
#}
I get the following error:
out-lineoutput : The object of type "Microsoft.PowerShell.Commands.Internal.For
mat.FormatStartData" is not valid or not in the correct sequence. This is likel
y caused by a user-specified "format-table" command which is conflicting with t
he default formatting.
+ CategoryInfo : InvalidData: (:) [out-lineoutput], InvalidOperat
ionException
+ FullyQualifiedErrorId : ConsoleLineOutputOutOfSequencePacket,Microsoft.P
owerShell.Commands.OutLineOutputCommand
I have seen similar error in powershell when using format-table however i am not using fr here atleast directly.
Not sure what is wrong!
EDIT:
no, but it turns out that the issue was not in the above script at all.
It appears that caller script had a line involving format-table which had nothing to do with this script, was causing the issue.
$SupportFilesInfo = Import-csv $SupportFilesCSV | select-object
$SupportFilesInfo | ft ; $SupportFilesInfo | Out-File -append $log
I changed it to:
$SupportFilesInfo | ft | out-default; $SupportFilesInfo | Out-File -append $log
which resolved the error!!
However i am still at loss at why the error occurs ONLY during the first run.
I had hit this issue earlier too, but it was very consistent.
Any idea why?