Powershell Applying SQL Patches Multiple Servers - sql-server

I need to apply SQL Server Patches in more than 300 Servers, so, I've created code below and saved it as Apply_SQL_Patch.ps1.
I'm reading a txt file with all servers names and I'd like to connect to them, extract and apply Patch.
The issue is when I execute it, it connect to server, but it's not changing directory to D:\Software\Patch, resulting in an error on next lines:
$output = foreach ($cluster in GC "D:\Software\Patch\Servers_List.txt")
{
Enter-PSSession -ComputerName $cluster
cd D:\Software\Patch\
.\SQLServer2014-KB4037356-x64.exe /X:D:\Software\Patch
.\setup.exe /action=patch /instancename=SQL2014 /quiet /IAcceptSQLServerLicenseTerms
}
$output | Out-File -Append D:\Software\Patch\Patch_Result.txt
Error below:
.\SQLServer2014-KB4037356-x64.exe : The term
'.\SQLServer2014-KB4037356-x64.exe' 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. At D:\software\patch\Apply_SQL_Patch.ps1:5
char:2
+ .\SQLServer2014-KB4037356-x64.exe /X:D:\Software\Patch
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (.\SQLServer2014-KB4037356-x64.exe:String) [],
CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
.\setup.exe : The term '.\setup.exe' 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. At D:\software\patch\Apply_SQL_Patch.ps1:7
char:2
+ .\setup.exe /action=patch /instancename=SQL2014 /quiet /IAcceptSQLServerLicense ...
+ ~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (.\setup.exe:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Thanks for your help!

Enter-PSSession -ComputerName $cluster
cd D:\Software\Patch\
.\SQLServer2014-KB4037356-x64.exe /X:D:\Software\Patch
.\setup.exe /action=patch /instancename=SQL2014 /quiet /IAcceptSQLServerLicenseTerms
I don't think this is going to work like you think. You're creating a session and then executing three commands locally.
Try:
Invoke-Command -ComputerName $cluster -ScriptBlock {
cd D:\Software\Patch\
Start-Process -PSPath '.\SQLServer2014-KB4037356-x64.exe' -ArgumentList '/X:D:\Software\Patch' -Wait
.\setup.exe /action=patch /instancename=SQL2014 /quiet /IAcceptSQLServerLicenseTerms
}
I've replaced the patch extraction command with the one above because the command returns control to PowerShell immediately. You may need to do the same thing with setup.exe. I don't have an SQL 2014 instance to test on.

Related

Issue with my powershell syntax to do an clean up

I am using below PowerShell code to delete the logs in database server path
powershell.exe -command & {
get-childitem -path "$(ESCAPE_SQUOTE(D:\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\Log\))" -filter *_*_*_*.txt |
Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-30)} | remove-item -verbose
}
Its erroring out as in below
At line:1 char:25
+ powershell.exe -command & {get-childitem -path "$(ESCAPE_SQUOTE(D:\Microsoft SQL ...
+ ~
The ampersand (&) character is not allowed. The & operator is reserved for future use; wrap an ampersand in double
quotation marks ("&") to pass it as part of a string.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : AmpersandNotAllowed
I am new to PowerShell and thought adding "" to "&" would suffice but its erroring again
powershell.exe : ScriptBlock should only be specified as a value of the Command parameter.
At line:1 char:1
+ powershell.exe -command "&" {get-childitem -path "$(ESCAPE_SQUOTE(D:\Microsoft S ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], ParameterBindingException
+ FullyQualifiedErrorId : IncorrectValueForCommandParameter
Added "" to the path but it doesn't help
powershell.exe -command & {get-childitem -path "$(ESCAPE_SQUOTE("D:\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\Log\"))" -filter *_*_*_*.txt | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-30)} | remove-item -verbose}
I need to call it from an SQL agent job under operating system command exec
Can any one help me with the syntax
Regards and Wishes
Eben
When passing commands to the PowerShell CLI's -Command / -c parameter:
If you're calling from outside PowerShell, omit & { ... } altogether, just specify ... - & { ... } is never needed and only creates overhead.
Additionally, if you're calling from cmd.exe (you're not), it's safest to double-quote ... ("..."), and to escape embedded double quotes as \" (sic).
From inside PowerShell, omit & (you do need { ... } to robustly pass the commands, but this approach only works when calling from inside PowerShell):
Your error message suggest that powershell.exe was indeed called from a PowerShell session.
However, the question is why you're calling another PowerShell instance - as a child process - given that you're already in a PowerShell session - you could just execute the statements inside { ... } directly.
Given your symptom, I'm wondering whether the exec command in your case actually already uses PowerShell rather than cmd.exe as the shell, in which case passing just the text inside { ... } from your question would be sufficient.

Getting a Cannot convert 'System.Object[]' to the type 'System.Management.Automation.ScriptBlock' calling powershell script from SQL Server

I am running this command/Script:
Exec xp_cmdshell 'powershell.exe Invoke-Command -ComputerName server01.client.local -ScriptBlock
{Get-Process myguiapp -IncludeUserName ^| Format-Table #{Expression={$_.username}}, #{Expression=
{$_.path}}, #{Expression={(((Get-Date) - $_.StartTime).ToString().Substring(0,8))}}}, #{Expression=
{$_.FileVersion.tostring()}}'
When I run it in powershell, it works fine, but when I run it in SSMS, I get this error:
Invoke-Command : Cannot convert 'System.Object[]' to the type
'System.Management.Automation.ScriptBlock' required by
parameter 'ScriptBlock'. Specified method is not supported.
At line:1 char:67
+ ... ScriptBlock {Get-Process myguiapp -IncludeUserName | Format-Table #{E ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Invoke-Command], ParameterBindingException
+ FullyQualifiedErrorId :
CannotConvertArgument,Microsoft.PowerShell.Commands.InvokeCommandCommand
And when I run it in SSMS without the last parameter (for FileVersion), it works fine:
Exec xp_cmdshell 'powershell.exe Invoke-Command -ComputerName zmcrds01.zmclient.local -ScriptBlock
{Get-Process zmfs.gui -IncludeUserName ^| Format-Table #{Expression={$_.username}}, #{Expression=
{$_.path}}, #{Expression={(((Get-Date) - $_.StartTime).ToString().Substring(0,8))}}}'
I'm running on SQL Server 2014 with Powershell 5.1
How can I fix this?

Powershell Value of argument path is NULL

I've developed a PS1 file which will be responsible to apply SQL Server Patches, based on a Server List. So, it'll read a text file with all servers I need to patch and apply Patch.
I've decided using PARAM for "Source Folder" ( where I'll get Server List and record output ); "Destination Folder" ( where I'll be able to run patch ), "File" ( name of patch ), "Instance" ( SQL Server Instance which I'll be running Patch update ).
When I start to run commands below, it's able to read Servers List ( so, 1st PARAM is ok ), but, it returns the error below aborting process.
What is missing or what am I doing wrong on code below?
PS.: I also would like to use Try...Catch to record a message on the output file. Did I write it correctly?
Thanks in advance!
[CmdletBinding()]
Param (
[Parameter(Mandatory=$True,Position=0)]
[string]$foldersource,
[Parameter(Position=1)]
[string]$folderdest,
[Parameter(Position=2)]
[string]$file,
[Parameter(Position=3)]
[string]$instance
)
foreach ($cluster in GC "$foldersource\Servers_List.txt")
{
$output = "Server: $cluster Patch Installation on: $(Get-Date -format 'u')"
try{
Invoke-Command -ComputerName $cluster -ScriptBlock
{
cd $folderdest
.\$file /X:$folderdest
Start-Sleep -s 10
.\SETUP.exe /action=patch /instancename=$instance /quiet /IAcceptSQLServerLicenseTerms
}
-ErrorAction Stop;
$output += " SUCCESS"
}
catch
{
$output += "Failed - $($_.exception.message)"
}
$output | Out-File -Append $foldersource\Patch_Result_Non_SP.txt
}
How I'm running command above: .\SQL_Server_install_non-Service_Pack_V2.ps1 "D:\Software\Patch" "D:\Software" "SQLServer2008R2-KB3045316-x64.exe" "MSSQLSERVER"
ERROR:
Cannot process argument because the value of argument "path" is null. Change the value of argument "path" to a non-null value.
+ CategoryInfo : InvalidArgument: (:) [Set-Location], PSArgumentNullException
+ FullyQualifiedErrorId : ArgumentNull,Microsoft.PowerShell.Commands.SetLocationCommand
+ PSComputerName :
The term '.\$file' 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: (.\$file:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
+ PSComputerName :
The term '.\SETUP.exe' 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: (.\SETUP.exe:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
+ PSComputerName :
You've to pass your arguments either via -ArgumentList or via $using convention to the Invoke-Command cmdlet. Since you are not doing it that way $folderdest, $file will be null in the scope of the Invoke-Command scriptblock -> the scriptblock defines a seperate scope!
From Microsoft:
-ArgumentList
Supplies the values of local variables in the command. The variables in the command are replaced by these values before the command is run on the remote computer. Enter the values in a comma-separated list. Values are associated with variables in the order that they are listed. The alias for ArgumentList is Args.
Also checkout the exmamples of the Invoke-Commandcmdlet via Get-Help Invoke-Command -Examples.
If you don't like the ArgumentList solution you can also use remote variables.
Additionally you should also define an absolute path to your Setup.exe!
So your code should look like:
....
Invoke-Command -ComputerName $cluster -ArgumentList $file, $folderdest, $instance -ScriptBlock
{
Param(
[string] $rFile,
[string] $rfileDest,
[string] $rInstance
)
# Remove Write-Host after the script block works fine -> Write-Host is only a quick and dirty way to dump the variables content
Write-Host $rFile
Write-Host $rfileDest
Write-Host $rInstance
cd $rfileDest
$someArgs = "/X:{0}" -f $rfileDest
Start-Process -FilePath $rFile -ArgumentList $someArgs -Wait -PassThru
Start-Sleep -s 10
$setupArgs = "action=patch /instancename={0} /quiet /IAcceptSQLServerLicenseTerms" -f $rInstance
Start-Process -FilePath ".\Setup.exe" -ArgumentList $setupArgs -Wait -PassThru
}
....
Hope that helps.

Remote execute sql script for one server to another

I have a "test.sql" in Server1, and I would like to run this script on server2 and server3. I utilized the code below:
$InstanceNm = "server2\inst2,1433"
$CommandExecute_SP = "D:\testscript\test.sql" --located in Server1
invoke-expression "SQLCMD -E -S $InstanceNm -d 'master' -i $CommandExecute_SP -b"
I run the all the codes from server1 and I target server2, but I am getting this error:
SQLCMD : Sqlcmd: 'server2\inst2,1433': Unexpected argument. Enter '-?' for help.
Then when i type -?
It gives this:
At line:1 char:2
+ -?
+ ~
Missing expression after unary operator '-'.
At line:1 char:2
+ -?
+ ~
Unexpected token '?' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingExpressionAfterOperator
I was wondering what the correct way is to run a .sql file which creates objects on another server from a separate server? I know how to run short sql statements via using System.Data.SQLClient.SQLConnection but I would like to avoid that since this .sql file has over 1000 lines of code.
You could create remote sessions and run the command via Invoke-Command-cmdlet.
$sessions = New-PSSession -computername server1, server2, server3
invoke-command -session $sessions -scriptblock {
invoke-expression "SQLCMD -E -S $InstanceNm -d 'master' -i $CommandExecute_SP -b"
}
If you've Win2012 R2 (or higher version) PS-remoting is activated by default. Otherwise take a look at remoting.
Hope that helps
I ran through myscript again and found out that the error was caused by the ' character in the 'master', I omitted this and it is working now.

How do I run a piped powershell command from command line?

I am trying to run the following powershell command from the windows 7 command line:
powershell ls 'C:/path to file/' | ForEach-Object {$_.LastWriteTime=Get-Date}
I have encountered several errors. When I run the above command, I get the error:
'ForEach-Object' is not recognized as an internal or external command,
operable program or batch file.
I changed the command to:
powershell ls 'C:/My Programs/CPU Analysis/data/test/' | powershell ForEach-Object {$_.LastWriteTime=Get-Date}
Now I am getting the error:
Property 'LastWriteTime' cannot be found on this object; make sure it exists
and is settable.
At line:1 char:17
+ ForEach-Object {$_.LastWriteTime=Get-Date}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFound
How can I modify this command to work from the command line?
Update
Both solutions are basically saying the same thing, but #Trevor Sullivan has a clearer answer.
cmd.exe doesn't understand foreach object. Plus, you're trying to split execution across two separate PowerShell processes, which is not going to work in this scenario.
You'll need to run the whole command in PowerShell.
powershell "ls 'C:/My Programs/CPU Analysis/data/test/' | ForEach-Object {$_.LastWriteTime = Get-Date}"
I'm not sure what are you trying to achieve..but if you are after files and their last modified time then use this:
powershell "ls 'C:\path' | ft name,LastWriteTime"
All you have to do is enclose your command in double quotes ".

Resources