I have a script, on which I connect to MicrosoftTeams (connect-microsoftteams)
since I have a long running operation, I am trying to run that code in a separate job. however, I do not know how I can pass along the already existing connection to MicrosoftTeams, to that scriptblock.
if (!$Connection) {$Connection = Connect-MicrosoftTeams}
$scriptBlocks = #{
$btns[0] =
{
param ([Parameter(Mandatory=$true)]$Users)
#Connect-MicrosoftTeams
$agents = #()
foreach ($a in $Users) {
$user = get-csonlineuser $a.ObjectID
if ($user) {
$agents += $user
}
}
return $agents
}
}
I launch this using:
foreach ($btn in $btns) {
$btn.Add_Click({
# Temporarily disable this button to prevent re-entry.
$this.IsEnabled = $false
# Show a status message in the associated text box.
$txtBoxes[$this].Visibility = "Visible"
# Asynchronously start a background thread job named for this button.
# Note: Would work with Start-Job too, but that runs the code in *child process*,
# which is much slower and has other implications.
$null = Start-Job -Name $this.Name $scriptBlocks[$this]
})
}
I later do some checks to see if the job completed.
However, this creates a new Powershell instance, that does not have the $Connection to MicrosoftTeams as the 'parent' has..
[edit]
I tried some more, like passing in a Get-Credential, this is almost willing to work.. except MFA is being a problem
$scriptBlocks = #{
$btns[0] = {
param ($Credential)
Connect-MicrosoftTeams -Credential $Credential
$global:AllCallQueues = Get-CsCallQueue -first 10000
}
}
this Produces:
Loaded Module 'Microsoft.Teams.ConfigAPI.Cmdlets'
One or more errors occurred.
AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access 'xxx'.
Trace ID: xxx
Correlation ID: xx
Timestamp: 2021-10-26 18:58:45Z
One or more errors occurred.
Exception calling "GetSteppablePipeline" with "1" argument(s): "Exception calling "GetRemoteNewCsOnlineSession" with "1" argument(s): "Run either Connect-MicrosoftTeams or new-csonlinesession before runnin
g cmdlets.""
all credits for this code go to https://stackoverflow.com/a/65531439/3014542
Related
Something strange is happening with a database server I had to rebuild and restore from backup.
I'm pointing an old VB6 application using ADODB.Connection and a modern C# EF6 application at it, using what should be the same connection string for both, of the form
servername\INSTANCE
When run on the same machine that's running SQL Server, the VB6 application and EF6 application are both able to connect using this connection string.
When run on a different machine on the network, the VB6 application connects, but the EF6 application doesn't.
(with a standard "server not found" message, error: 26 - Error Locating Server/Instance Specified at System.Data.SqlClient.SqlInternalConnectionTds..ctor)
If I look at the specific instance port and connect with
servername,instance_port_number
then both applications connect, whatever machine I run them on. So it seems like something might be happening with SQL Server Browser to cause the issue.
Is there a way I can get some kind of diagnostic information out of SQL Server Browser, what data it's sent to where, without going as far as to monitor all network traffic?
An alternative to a network trace for troubleshooting is to send an instance enumeration query to the SQL Server Browser service and examine the results. This will verify the SQL Server Browser is reachable over UDP port 1434 and that the returned datagram contains the instance name and port information needed for the client to connect to a named instance.
Run the PowerShell script below on the problem machine.
# verify UDP port 1433 connectivity and query SQL Server Browser for all instances
Function Get-SqlServerBrowerDatagramForAllInstances($hostNameOrIpAddress)
{
Write-Host "Querying SQL Browser for all instances on host $hostNameOrIpAddress ..."
try
{
$udpClient = New-Object Net.Sockets.UdpClient($hostNameOrIpAddress, 1434)
$bufferLength = 1
$browserQueryMessage = New-Object byte[] 1
$browserQueryMessage[0] = 2
$bytesSent = $udpClient.Send($browserQueryMessage, $browserQueryMessage.Length)
$udpClient.Client.ReceiveTimeout = 10000
$remoteEndPoint = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Broadcast, 0)
$browserResponse = $udpClient.Receive([ref]$remoteEndPoint)
$payloadLength = $browserResponse.Length - 3
$browserResponseString = [System.Text.ASCIIEncoding]::ASCII.GetString($browserResponse, 3, $payloadLength)
$elements = $browserResponseString.Split(";")
Write-Host "SQL Server Browser query results:`r`n"
for($i = 0; $i -lt $elements.Length; $i = $i + 2)
{
if ($elements[$i] -ne "")
{
Write-Host "`t$($elements[$i])=$($elements[$i+1])"
}
else
{
Write-Host ""
# next instance
$i = $i - 1
}
}
}
catch [Exception]
{
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
}
}
# verify UDP port 1433 connectivity and query SQL Server Browser for single instance
Function Get-SqlServerBrowerDatagramByInstanceName($hostNameOrIpAddress, $instanceName)
{
Write-Host "Querying SQL Browser for host $hostNameOrIpAddress, instance $instanceName ..."
try
{
$instanceNameBytes = [System.Text.Encoding]::ASCII.GetBytes($instanceName)
$udpClient = New-Object Net.Sockets.UdpClient($hostNameOrIpAddress, 1434)
$bufferLength = $InstanceNameBytes.Length + 2
$browserQueryMessage = New-Object byte[] $bufferLength
$browserQueryMessage[0] = 4
$instanceNameBytes.CopyTo($browserQueryMessage, 1)
$browserQueryMessage[$bufferLength-1] = 0
$bytesSent = $udpClient.Send($browserQueryMessage, $browserQueryMessage.Length)
$udpClient.Client.ReceiveTimeout = 10000
$remoteEndPoint = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Broadcast, 0)
$browserResponse = $udpClient.Receive([ref]$remoteEndPoint)
$payloadLength = $browserResponse.Length - 3
$browserResponseString = [System.Text.ASCIIEncoding]::ASCII.GetString($browserResponse, 3, $payloadLength)
$elements = $browserResponseString.Split(";")
$namedInstancePort = ""
Write-Host "SQL Server Browser query results:`r`n"
for($i = 0; $i -lt $elements.Length; $i = $i + 2)
{
if ($elements[$i] -ne "")
{
Write-Host "`t$($elements[$i])=$($elements[$i+1])"
if($elements[$i] -eq "tcp")
{
$namedInstancePort = $elements[$i+1]
}
}
}
}
catch [Exception]
{
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
}
}
Get-SqlServerBrowerDatagramForAllInstances -hostNameOrIpAddress "servername"
Get-SqlServerBrowerDatagramByInstanceName -hostNameOrIpAddress "servername" -instanceName "INSTANCE"
In entity framework 6 you can take the dbcontext object and do something like. Yourcontext.Database.log = s => mylogger.Debug(s);
The right hand side is a lambda function that takes a string s and logs it.
All of the sql and parameters get logged.
There's nothing to suggest that a simple Windows Forms DragDrop won't work in PowerShell, and several resources explaining that it does work, however I cannot get any of them working. Even something as simple as this:
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$form = New-Object System.Windows.Forms.Form
$TBcode = New-Object System.Windows.Forms.TextBox
$form.Controls.Add($TBcode)
$TBcode.Dock = "Fill"
$TBcode.Multiline = $true
$TBCode.ScrollBars = "Vertical"
$TBCode.AllowDrop = $true
$TBcode.Add_DragEnter({ Write-Host "DragEnter"; $_.Effects = "Copy" })
$TBcode.Add_DragDrop({ Write-Host "DragDrop" })
$TBcode.Add_MouseEnter({ Write-Host "Mouse Enter" })
$form.ShowDialog()
The MouseEnter event triggers normally however when attempting to drag anything in to the TextBox nothing happens.
I had a sudden realisation and was able to confirm that this is in fact User Account Control.
Because I'm on Windows 10 I've got my PowerShell running as Administrator so I can actually do what I need to, however an object from the User-level Explorer process (or anything else running as a user) will simply refuse to interact with the Administrator-level PowerShell form.
Running PowerShell as a user allows the DragDrop to work as expected, but good luck if you need it to be admin! >_>
I'm trying to build a script that will display a WPF window to a user indicating progress. The script itself will run from the system account, but will need to show progress to the end user. The script will be launched in the System account either through PSExec or SCCM (Note in both cases the initial script can't be run with 'user interaction' enabled. Yeah. I know. Its a requirement though).
Is there a way to create that window from the System context so that a user can interact with it? Alternatively, can a Runspace be opened in another User's context? Or are neither of these a viable route?
Something like:
Start-Process powershell.exe -credential {cred. maybe a stored cred?} -nonewwindow -working directory {wherever you want it to start from. cred value must have access} -argumentlist "-file yourfile.ps1"
"yourfile.ps1" would have the code block for interacting with the user. -nonewwindow is of course up to you. I'm not sure how you'd communicate across userspace. Im still more sysadmin than programmer so I'd do something hacky. Like re-draw the window every 10% or something. Depends what feedback you want from the user.
The only solution I found was this function: Send-TSMessageBox.
http://pinvoke.net/default.aspx/wtsapi32.WTSSendMessage
This could be run as SYSTEM but shows up on the user's desktop.
One downside: On a virtual machine, the message box shows up in session 0 (which is the Hyper-V "integrated" connection window). If you are connected via RDP (mstc) session you will not see the message box.
But on a Citrix desktop it works. The message box pops up within the user session and not on the Citrix host.
Here's the full function:
Function Send-TSMessageBox {
<#
.SYNOPSIS
Send a message or prompt to the interactive user with the ability to get the results.
.DESCRIPTION
Allows the administrator to send a message / prompt to an interactive user.
.EXAMPLE
"Send a message immediately w/o waiting for a responce."
Send-TSMessageBox -Title "Email Problem" -Message "We are currently having delays and are working on the issue."
"Send a message waiting 60 seconds for a reponse of [Yes / No]."
$Result = Send-TSMessageBox -Title "System Updated" -Message "System requires a reboot. Would you like to the reboot system now?" `
-ButtonSet 4 -Timeout 60 -WaitResponse $true
.ButtonSets
0 = OK
1 = Ok/Cancel
2 = Abort/Retry/Ignore
3 = Yes/No/Cancel
4 = Yes/No
5 = Retry/Cancel
6 = Cancel/Try Again/Continue
.Results
"" = 0
"Ok" = 1
"Cancel" = 2
"Abort" = 3
"Retry" = 4
"Ignore" = 5
"Yes" = 6
"No" = 7
"Try Again" = 10
"Continue" = 11
"Timed out" = 32000
"Not set to wait" = 32001
.NOTES
Author: Raymond H Clark
Twitter: #Rowdybullgaming
.RESOURCES
http://technet.microsoft.com/en-us/query/aa383488
http://technet.microsoft.com/en-us/query/aa383842
http://pinvoke.net/default.aspx/wtsapi32.WTSSendMessage
#>
Param([string]$Title = "Title", [string]$Message = "Message", [int]$ButtonSet = 0, [int]$Timeout = 0, [bool]$WaitResponse = $false)
$Signature = #"
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSSendMessage(
IntPtr hServer,
[MarshalAs(UnmanagedType.I4)] int SessionId,
String pTitle,
[MarshalAs(UnmanagedType.U4)] int TitleLength,
String pMessage,
[MarshalAs(UnmanagedType.U4)] int MessageLength,
[MarshalAs(UnmanagedType.U4)] int Style,
[MarshalAs(UnmanagedType.U4)] int Timeout,
[MarshalAs(UnmanagedType.U4)] out int pResponse,
bool bWait);
[DllImport("kernel32.dll")]
public static extern uint WTSGetActiveConsoleSessionId();
"#
[int]$TitleLength = $Title.Length;
[int]$MessageLength = $Message.Length;
[int]$Response = 0;
$MessageBox = Add-Type -memberDefinition $Signature -name "WTSAPISendMessage" -namespace "WTSAPI" -passThru
$SessionId = $MessageBox::WTSGetActiveConsoleSessionId()
$MessageBox::WTSSendMessage(0, $SessionId, $Title, $TitleLength, $Message, $MessageLength, $ButtonSet, $Timeout, [ref] $Response, $WaitResponse)
$Response
}
Unfortunately the design of the messsage box is very limited. Actually it looks ugly :-)
I have a couple of scripts that have to sync a folder from the network server, to the local terminal server, and lastly into the %LOCALAPPDATA%. I need to first check if a folder is being synced (this is done by creating a temporary COPYING.TXT on the server), and wait until that is removed, THEN copy to %LOCALAPPDATA%.
Something like this:
Server-side script executes, which syncs my folder to all of my terminal servers.
It creates a COPYING.TXT temporary file, which indicates the sync is in progress. Once the sync is finished, the script removes the COPYING.TXT
If someone logs on during the sync, I need a script to wait until the COPYING.TXT is deleted I.E the sync is finished, then resume the local sync into their %LOCALAPPDATA%.
do {
cp c:\folder\program $env:LOCALAPPDATA\
} while ( !(test-path c:\folder\COPYING.txt) )
(So that copies the folder while the file DOESN'T exist, but I don't think that exits cleanly)
Or:
while ( !(test-path c:\folder\COPYING.txt) ) {
cp c:\folder\program $env:LOCALAPPDATA\ -recurse -force
if ( !(test-path c:\folder\program) ) {return}
}
But that script quits if the COPYING.TXT exists. I think I need to create a function and insert that function within itself, or a nested while loop, but that is starting to make my head hurt.
As Mosser Lee said, try using the FileSystemWatcher class. Here is a working sample.
#Create the Copying.txt file
"test"|Out-File Copying.txt
#Create a filesystemwatcher
$watcher = New-Object System.IO.FileSystemWatcher
#Give it the root path to monitor
$watcher.Path = $pwd
#The matching pattern
$watcher.Filter = "Copying.txt"
#Monitor subfolder or not
$watcher.IncludeSubdirectories = $true
#Setup event and save a ref
$evt = Register-ObjectEvent $watcher Deleted FileDeleted -Action {
#Stuff it into the global space just so you can inspect it
$global:SomeVar = $Event
Write-Host ("{0} deleted at {1}" -f $Event.SourceEventArgs.FullPath, $Event.TimeGenerated)
}
Remove-Item Copying.txt
This gives output like "H:\Copying.txt deleted at 6/12/2014 3:01:48 PM" when the file is deleted. It also sets a global variable $global:SomeVar if you wanted to look at the properties in depth.
$global:SomeVar
ComputerName :
RunspaceId : 1ab5089e-1734-4b92-8bab-9de4df78ada2
EventIdentifier : 2
Sender : System.IO.FileSystemWatcher
SourceEventArgs : System.IO.FileSystemEventArgs
SourceArgs : {System.IO.FileSystemWatcher, Copying.txt}
SourceIdentifier : FileDeleted
TimeGenerated : 6/12/2014 3:01:48 PM
MessageData :
Don't forget to unregister the event as it will continue to run until you close the session even if you set $watcher to null.
Unregister-Event $evt.Id
Did you have a try to use c# FileSystemWatcher, to monitor the target folder, when change event raised, then check the target file, if it no exits, your expected time is comming: do it.
I am trying to display an image via powershell. I made a script based on this forum post.
If I use ShowDialog() it works fine, except the powershell execution stops while the dialog is up. However, that is by design for a modal dialog. If I call Form.Show() in PowershellISE the form shows up, but freezes and cannot be moved or dismissed. Behavior is similar if I copy and past the code to a powershell console.
How do I make the dialog non-modal, and not freeze.
First Answer Why it appends.
In a Windows graphic program the thread which create a Window must loop in a message pump in order to redistribute (translate) messages coming from the user action to events in his Windows.
In a modal window, the modal code that handles the window display runs its own message pump loop and doesn't return until the window is closed. That's why the code after ShowDialog() won't execute until the window is closed.
Show(), just ask to show the Window, but if there is no pump loop to manage the messages coming from user action, it just freezes.
Second a simple way to have two threads
The CmdLet start-job use another thread from the pool allocated by Powershell so it makes the dialog non-modal, and it does not freeze.
function goForm
{
[void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
$file = (get-item 'C:\temp\jpb.png')
#$file = (get-item "c:\image.jpg")
$img = [System.Drawing.Image]::Fromfile($file);
# This tip from http://stackoverflow.com/questions/3358372/windows-forms-look-different-in-powershell-and-powershell-ise-why/3359274#3359274
[System.Windows.Forms.Application]::EnableVisualStyles();
$form = new-object Windows.Forms.Form
$form.Text = "Image Viewer"
$form.Width = $img.Size.Width;
$form.Height = $img.Size.Height;
$pictureBox = new-object Windows.Forms.PictureBox
$pictureBox.Width = $img.Size.Width;
$pictureBox.Height = $img.Size.Height;
$pictureBox.Image = $img;
$form.controls.add($pictureBox)
$form.Add_Shown( { $form.Activate() } )
$form.ShowDialog()
}
Clear-Host
start-job $function:goForm
$name = Read-Host "What is you name"
Write-Host "your name is $name"
There are ways to make this work, but nothing is worth spending five hours explaining on an open forum. There are other free, shrink-wrapped ways to do this on powershell. Most notably with the free WPF powershell toolkit: Show-UI at http://showui.codeplex.com/ (previously known as WPK and/or PowerBoots - they are merged now.)
If your goal is actually to not block the interactive console when an image is shown then you still can use the script as it is with ShowDialog but you should start it using, for example, Start-Job. Thus, the dialog is still modal but it blocks execution in another runspace. The main runspace still can be used for invoking other commands.
Caveats: 1) You should close all opened dialogs before closing the interactive console. 2) If you care, you should remove completed jobs yourself (when a dialog is closed a job that started it still exists).
I use a similar approach in my custom host and it works fine. I also tested it with the script from your link. I changed it slightly so that it is called show-image.ps1 and accepts a file path as a parameter.
This command shows an image and blocks the calling runspace:
show-image.ps1 C:\TEMP\_110513_055058\test.png
This command shows an image and does not block the calling runspace:
Start-Job { show-image.ps1 $args[0] } -ArgumentList C:\TEMP\_110513_055058\test.png
Building on #JPBlanc's anwer, this would also be possible (and faster) using a runspace.
Here's a basic example (the rest would basically remain the same)
$ps = [PowerShell]::Create()
[void]$ps.AddScript({
Add-Type -AssemblyName System.Windows.Forms
$form = [Windows.Forms.Form]::new()
$form.ShowDialog()
})
[void]$ps.BeginInvoke()