PowerShell while loop when function returns value - powershell

I have several steps in powershell invoked against sql server.
backup database
add database to availability group
turn on NAV middle tier
Problem is, that add to availability group step just executes command and does not wait until database is recovered on secondary nod. I have a function to check state of database on secondary and this function should wait until database is online and then start NAV middle tier.
I get correct database state (ONLINE/RECOVERING...), ,but somehow I cannot figure out WHILE part. My example here runs even if database is ONLINE or OFFLINE.
What Am doing wrong?
function get-dbstate($db_name_restore){
$query_check_state = "select state_desc from sys.databases where [name] = '"+ $db_name_restore +"'"
$state = invoke-sqlcmd -ServerInstance server2 -Query $query_check_state -ErrorAction Stop
return $state.state_desc
}
while(get-dbstate($db_name_restore) -ne "ONLINE"){
write-host("...still restoring")
Start-Sleep -Seconds 2
}

Ok correct syntax is to put function into brackets:
while((get-dbstate $db_name_restore) -ne "ONLINE"){
write-host "...still restoring"
Start-Sleep -Seconds 2
}

Related

PowerShell Remoting, Eventing, Runspace - sticky Event Subscriber

I am currently stuck at a problem involving a WMI Event subscriber on a remote session running in a background runspace.
Below is the representative code that reproduces the issue (the real code is too long to share here) but, essentially, through a PS script I remotely install advertised WSUS updates with reboots when necessary. It takes about 90 minutes end to end.
The issue I am trying to solve at the moment is that during the course of patching, the support staff inadvertently log in to the the server being remotely patched and do their admin activities. To remind them, I want to display a pop up message as soon as the user logs in when my remote script is running. I am trying to do it using a background runspace plugged into the main patching script. It makes use of WMI eventing on the target server (which is being patched) to monitor user logons and display message as soon as it detects one. The below code is working as I expect. It even survives target server reboots.
$RemoteServerName = 'Server1.contoso.com'
$UserLogonAlertScriptBlock = {
param ($SyncedHashTable)
$RemoteServerName = $SyncedHashTable.target
try {
$Session = New-PSSession -ComputerName $RemoteServerName -ErrorAction Stop
} catch {}
while($true){
if($Session.State -eq 'Opened') {
$RemoteMonitoringJob = Invoke-Command -Session $Session -AsJob -ScriptBlock {
$null = Register-WMIEvent -Query "SELECT * FROM __InstanceCreationEvent WITHIN 3 WHERE TargetInstance ISA 'Win32_LogonSession'" -SourceIdentifier 'User.Logon'
Wait-Event -SourceIdentifier "User.Logon" -Timeout 7200 | ForEach-Object {
msg * /TIME:7200 /V "User logon detected" | Out-Null
$_ | Remove-Event
}
}
while($RemoteMonitoringJob.State -in #('NotStarted','Running')) {
Start-Sleep -Seconds 1
}
} else {
while($true) {
try {
$Session = New-PSSession -ComputerName $RemoteServerName -ErrorAction Stop
} catch {}
if($Session.State -eq 'Opened') {
break
}
Start-Sleep -Seconds 1
}
}
}
}
$Runspace = [runspacefactory]::CreateRunspace()
$PowerShell = [powershell]::Create()
$PowerShell.runspace = $Runspace
$SyncedHashTable = [hashtable]::Synchronized(#{})
$SyncedHashTable.host = $host
$SyncedHashTable.target = $RemoteServerName
$Runspace.Open()
$handle = $PowerShell.AddScript($UserLogonAlertScriptBlock).AddArgument($SyncedHashTable).BeginInvoke()
Write-Host '$(Get-Date): Long running script execution targeting $RemoteServerName has started'
Start-Sleep -Seconds 120 # it usually runs for upto 90 minutes, with remote reboots of $RemoteServerName
Write-Host "$(Get-Date): The script execution has completed"
### The code that cleans up the sticky event subscriber on the target server needs to be added here
The part I am stuck at is after the script completes its execution. The wsmanprovhost.exe running on the target server continues to stick around and shows alert messages when new users log on. I think it's because of the WMI event listener still being active on the box, not releasing the remote PS session.
In the above code, I need help close that remote listener so wsmanprovhost.exe disappears.
Could you please help?
PS. I have referred to #mklement0 's response in the following post but still no joy: The RunSpace and its closure
Update:
I have managed to address the challenge by adding a Boolean flag into the SyncedHashtable which is passed to the background runspace. When I want to stop the remote logon monitoring, in the main script I flip the flag. Since it's inside a synced hashtable, I can monitor that inside the runspace and terminate the remote invoke command job in the run space. But I still had to forcibly kill the remote wsmprovhost.exe as it refuses to go. I could do it by getting the pid of the remote PS session in advance. Not the most elegant way to close a remote PS session but it does the job for me. It's just that since the remote session is continuously monitoring for user logon event, there does not appear to be a way to run a piece of code in that session to unsubscribe the WMI event source. Will do more testing to see if there is any side effect.

PowerShell script does not run from TaskScheduler

I have a script that pulls some information from AD, inserts rows into a temp table, then calls a SQL script that transforms and upserts rows into a crosswalk table. The script runs fine in ISE, but fails when running in TaskScheduler, whether manually run or scheduled.
On the 'ACTION' page, the program is 'powershell.exe', and the arguments are '-executionpolicy bypass C:\scripts\SysManagement\Populate_AD_Xwalk.ps1.' The Last Run Result is (0x1).
Any idea what is wrong?
Thanks
# Invoke-sqlcmd Connection string parameters
$params = #{'server'='xxx';'UserName'='xxx';'Password'='xxx'; 'Database'='xxx'}
######################
# Function to manipulate the data
Function writeDiskInfo
{
param($UPN,$EMAIL,$SAM,$ACTIVE)
$InsertResults = #"
INSERT INTO [xxx].[dbo].[WORK_UPN_Email](UPN, EMAIL, SAM, ACTIVE)
VALUES ('$UPN','$EMAIL','$SAM', '$ACTIVE')
"#
# call the invoke-sqlcmdlet to execute the query
Invoke-sqlcmd #params -Query $InsertResults
}
#####################
# Query AD objects and store in an array
$dp = Get-ADUser -property 'emailaddress' -Filter *
# Loop through array and insert into WORK table
foreach ($item in $dp)
{
# Call the function to transform the data and prepare the data for insertion
writeDiskInfo $item.UserPrincipalName $item.EmailAddress $item.SamAccountName $item.Enabled
}
# Call SQL procedure to delete rows with blank upns and upsert crosswalk table
Invoke-Sqlcmd #params -Query "EXEC ZZproc_Upsert_AD_Email"
The Last Run Result is (0x1) , could mean it’s a privilege issue.
Check what user is the scheduler running as . Can the scheduler run even if the user is not logged in?
I believe you are using AD user for sql operations. Is the scheduler running as that user. OR does the user running scheduler have sufficient DB privileges ?
Let’s say scheduler is being run as the AD user, then check if the user has sufficient privileges to the folder where the powershell script resides
Under system32 folder you have a Folder “Tasks” . Does this user have read and execute privileges to the Tasks folder
Most importantly the user running scheduler should have the privilege “Log on as batch job”

Using Powershell to find last step run from an SQL agent job

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
}
}

Web service call, if application is running

I'm looking for a way to execute a web form submittal if an application is running. I'm not sure the best approach, but I did create a PowerShell script that accomplishes what I want.
while($true) {
(Invoke-WebRequest -Method post 'Http://website.com').Content;
Start-Sleep -Seconds 600;
}
Now what I'd like to do is run this only when an application is running and then quit if the application is no longer running. I suspect maybe a Windows service would be the answer? If so, any idea how I could accomplish this?
I had also thought about running this as a Google Chrome extension, but then my googlefu was exhausted. For Chrome, I would just need the script and no need to check on the .exe.
Any thoughts or help would be appreciated. Again, I'm way out of my depth here but have found a need to create something so dummy steps would be much desired.
If you know the name of the process that runs for the application, you can do the following:
$processname = "thing"
# Wait until the process is detected
Do {
Sleep 60
} Until (Get-Process $processName)
# Once it is detected, run the script
# < SCRIPT RUN CODE HERE >
While (1) {
# Monitor the process to make sure it is still running
If (Get-Process $processName) {
Continue
}
Else {
# Stop the script, because the process isn't running.
# < SCRIPT STOP CODE HERE >
# Wait until the process is detected again
Do {
Sleep 60
} Until (Get-Process $processName)
# Once it is detected again, run the script
# < SCRIPT RUN CODE HERE >
}
# You can add in a delay here to slow down the loop
# Sleep 60
}
I think what you're looking for might be WMI eventing. You can register for (and respond to) events that occur within WMI, such as:
When a process starts or stops
When a service starts or stops
When a process exceeds a certain amount of memory usage
When a new version device driver is installed
When a computer is assigned to a new organizational unit
When a user logs on or off
When an environment variables changes
When a laptop battery drops below a certain threshold
Thousands of other cases
To register for WMI events, use the Register-WmiEvent cmdlet. You can use the -Action parameter to declare what PowerShell statements to execute when a matching event is detected. Here is a simple example:
# 1. Start notepad.exe
notepad;
# 2. Register for events when Notepad disappears
# 2a. Declare the WMI event query
$WmiEventQuery = "select * from __InstanceDeletionEvent within 5 where TargetInstance ISA 'Win32_Process' and TargetInstance.Name = 'notepad.exe'";
# 2b. Declare the PowerShell ScriptBlock that will execute when event is matched
$Action = { Write-Host -ForegroundColor Green -Object ('Process stopped! {0}' -f $event.SourceEventArgs.NewEvent.TargetInstance.Name) };
# 2c. Register for WMI events
Register-WmiEvent -Namespace root\cimv2 -Query $WmiEventQuery -Action $Action -SourceIdentifier NotepadStopped;
# 3. Stop notepad.exe
# Note: For some reason, if you terminate the process as part of the same thread, the event
# doesn't seem to fire correctly. So, wrap the Stop-Process command in Start-Job.
Start-Job -ScriptBlock { Stop-Process -Name notepad; };
# 4. Wait for event consumer (action) to fire and clean up the event registration
Start-Sleep -Seconds 6;
Unregister-Event -SourceIdentifier NotepadStopped;
FYI: I developed a PowerShell module called PowerEvents, which is hosted on CodePlex. The module includes the ability to register permanent WMI event subscriptions, and includes a 30+ page PDF document that helps you to understand WMI eventing. You can find this open-source project at: http://powerevents.codeplex.com.
If I were to adapt your code to something that is more practical for you, it might look something like the example below. You could invoke the code on a periodic basis using the Windows Task Scheduler.
# 1. If process is not running, then exit immediately
if (-not (Get-Process -Name notepad)) { throw 'Process is not running!'; return; }
# 2. Register for events when Notepad disappears
# 2a. Declare the WMI event query
$WmiEventQuery = "select * from __InstanceDeletionEvent within 5 where TargetInstance ISA 'Win32_Process' and TargetInstance.Name = 'notepad.exe'";
# 2b. Declare the PowerShell ScriptBlock that will execute when event is matched
# In this case, it simply appends the value of the $event automatic variable to a
# new, global variable named NotepadEvent.
$Action = { $global:NotepadEvent += $event; };
# 2c. Register for WMI events
Register-WmiEvent -Namespace root\cimv2 -Query $WmiEventQuery -Action $Action -SourceIdentifier NotepadStopped;
# 3. Wait indefinitely, or until $global:NotepadEvent variable is NOT $null
while ($true -and -not $global:NotepadEvent) {
Start-Sleep -Seconds 600;
(Invoke-WebRequest -Method post 'Http://website.com').Content;
}

Powershell Invoke-sqlcmd keeping connection open and trying to reuse it

I have a Powershell script that uses invoke-sqlcmd to apply scripts to a series of development databases. I loop through a list of scripts and compare it to the current release level of the database and then apply the required scripts to get the DB to the release level it needs to be at. Certain databases are reference databases and are in a READ_ONLY state. I connect to those database run an alter DB script setting them to READ_WRITE apply the script then change the back to READ_ONLY. Overall the script works well, the issue is it looks like when PowerShell first opens a connection to the database and applies the first script and then goes to alter the DB back to READ_ONLY the database has objects locked. I've traced it back to the previous connection and a Shared_Transaction_Workspace lock (sys.dm_tran_locks) for what looks to be the previous powershell connection. Why is this connection still open after the invoke-sqlcmd has completed and is there anything I can do about it? Can I force invoke-sqlcmd to use a new connection for each invocation of the cmdlet?
I have tried a messy fix killing the offending connection and then retrying the connection but I think there is something better.
I've always done this and it seems to work:
[System.Data.SqlClient.SqlConnection]::ClearAllPools()
Well, I know that this is a very old post and the people from Microsoft told that fixed this issue (as told the article mentioned by David Brabant) but maybe I'm not the luckiest guy and have to make an workaround to make it happens.
Even running Microsoft SQL Server 2012 (SP1) - 11.0.3128.0 (X64) I had the same issue and after make some researches I got a way to get some parameter from Invoke-Sqlcmd as output so I can get the Session ID of the current user process with the built-in ##SPID global variable from the SQL Server and make a connection with ADO.NET to execute a KILL clause to close the opened connection.
So let's to the workaround applied in my case
#Invoke the Invoke-Sqlcmd to execute an script from a file
Invoke-Sqlcmd -Server "[SERVER_NAME]" -Database [DATABASE_NAME] -Username [USER] -Password [PASSWORD] -InputFile [DOT_SQL_FILE_PATH]
#Invoke the Invoke-Sqlcmd to execute a inline SQL statement to get the SessionID as a Powershell variable
$SQLSession = Invoke-Sqlcmd -Server "[SERVER_NAME]" -Database [DATABASE_NAME] -Username [USER] -Password [PASSWORD] -query "select ##spid as SessionID"
# Build query to execute KILL clause
$DbQuery = "KILL " + $SQLSession.SessionID;
# Create SQL connection with ADO.NET
$DbConnection = New-Object System.Data.SqlClient.SqlConnection
$DbConnectionString = "Server = [SERVER_NAME]; Database = [DATABASE_NAME]; User ID=[USER]; Password=[PASSWORD];"
$DbConnection.ConnectionString = $DbConnectionString
$DbConnection.Open()
# Create SQL command for KILL clause
$DbCommand = New-Object System.Data.SQLClient.SQLCommand
$DbCommand.Connection = $DbConnection
$DbCommand.CommandText = $DbQuery
# Execute KILL clause
$DbCommand.ExecuteNonQuery()
# Close connection
$DbConnection.Close()
I hope that it helps
Even though I am using the newest version of SSMS (Version 16.5.3 - Build 13.0.16106.4), I still get this issue. I haven't figured out what the "right" way of forcing the connection closed is, but I have a work-around that is simple and resolves the issue for me. If you just need to get the connection off the database, you can do the following:
Run normal command(s)
Invoke-Sqlcmd -ServerInstance "SOME_SERVER" -Database "SOME_DB" ...
When you are ready to eliminate the connection from the database:
Invoke-Sqlcmd -ServerInstance "SOME_SERVER" -Database "SOME_DB" -Query "use [master];"
This will switch the connection to master, thus removing it from the database of interest. If you absolutely need the connection closed, I think you need to resort to SqlClient or such.