Stop user from opening an already program using PowerShell - windows

I want to stop the user from running another instance of an already running program/service in Windows using PowerShell.
Eg: I have notepad opened, then for minute's time period I want to disable the option to open notepad, since it already is running.
As of now, I can detect if the program is open or not, and if not I may have it opened for user (code attached).
$processName = Get-Process notepad -ErrorAction SilentlyContinue
if ( $processName ) {
Write-Host 'Process is already running!'
#stop another instance of notepad to be opened, since it is already running
}
else {
$userChoice = Read-Host 'Process is not running, should I start it? (Y/N) '
if ($userChoice -eq 'Y') {
Start-Process notepad
}
else {
Write-Host 'No Problem!'
}
}
But, how can I disable the option for the user to open another instance of the same?
Any lead for the same would be helpful.

Since Windows doesn't have such a feature that prevents launching multiple copies of an executable, you have two options:
poll process list every now and then. Terminate extra instances of the application
create a wrapper to the application and use a mutex to prevent multiple copies
The first option has its caveats. If additional copies are launched, it takes on the average half the polling interval to detect those. What's more, which of the processes are to be terminated? The eldest? The youngest? Some other criteria?
The second one can be circumvented easily by just launching the application itself.
The only real solution is to implement a single-instance feature in the application itself. Games often do this. For business software, be wary that the users will hate you, if there is a single reason why running multiple instances would be of use. Yes, especially if that use case would be absurd.
As an example of a mutex-based launcher, consider the following function
function Test-Mutex {
$mtx = new-object System.Threading.Mutex($false, "SingleInstanceAppLauncher")
if(-not $mtx.WaitOne(0, $false)) {
write-host "Mutex already acquired, will not launch second instance!"
read-host "Any key"
return
}
write-host "Running instance #1"
read-host "Any key"
# Do stuff
}
As like the solution 2 caveat, any user can work around the limit by just executing the do suff part. Remember, the wrapper prevents launching multiple instances of the wrapper, not about the do stuff.

Related

Have program execution switch between computers/servers

I've got two servers and a program that I want to run on them (not necessarily simultaneously).
Let's call one server "SA" and the other one "SB".
SB is a backup for SA, and I want that while my program is executing on SA, if SA fails then the program will immediately pick up where it left off and continue executing on SB.
What is the easiest way I can accomplish this?
There are probably a bunch of ways that this could be done, but I'd use an exclusive file lock to do it. To make that happen, you need enough network connectivity between the two servers that both could open a file for writing to.
Your basic algorithm (pseudocode) goes like this:
File f;
while (true) {
myTurn = false
try {
Open Network file for writing
myTurn = true;
} catch (IOException e) {
// not logging anything because this is expected.
// you might log that you tried maybe
myTurn = false;
}
if ( myTurn ) {
Do all of your actual work here.
loop many times if that's what you're doing.
don't exit this bit until your server wants to shut down
(or crashes).
But don't close the file
}
}
Basically what happens is that your app tries to open a file exclusively.
If it can't open it, then the other server is locked, so this server should stay quiet.
If it can open the file, then the other server is not running and this server should do the work.
For this to work, it's absolutely essential that the "work" routine, does not hang - as long as the other server's process is active, it will hang onto that network file lock. So if the other server goes into an infinite loop, you'll be out of luck.
And remember, both servers are trying to open the same network file. If they're trying to open a local file, it's not going to work.
This question has an example that you could probably re-use:
Getting notified when a file lock is released

Determining that Package Manager Console is ready when automating Visual Studio using EnvDTE

With some, limited, success, I'm able to automate VS to execute some arbitrary PS in the Package Manager Console. The following is roughly the code I have running so far:
MessageFilter.Register()
try
{
var type = Type.GetTypeFromProgID("VisualStudio.DTE.14.0");
var comObj = Activator.CreateInstance(type);
var dte = (DTE2) comObj;
try
{
dte.MainWindow.Activate();
Thread.Sleep(10000); // WAIT 1
dte.Solution.Open("path/to/sln");
dte.ExecuteCommand("View.PackageManagerConsole");
Thread.Sleep(10000); // WAIT 2
dte.ExecuteCommand("View.PackageManagerConsole", "Start-Sleep -Seconds 10")
Thread.Sleep(10000); // WAIT 3
dte.Solution.Close();
}
finally
{
dte.Quit()
}
}
finally
{
MessageFilter.Revoke();
}
Is there some way to remove the waits and subscribe to some events when some operation has completed? I'm especially curious about WAIT 2 and WAIT 3 above, which would occur after PMC commands (first to open it, and then to invoke the PS command).
Unrelatedly, is there a way of doing all this without having to make the VS window active? I suspect not, because I get an exception without dte.MainWindow.Activate().
My end goal is to be able to run Update-Package with a specific version for a given package and solution, although running any arbitrary PMC command would have other advantages as well. Unfortunately nuget.exe doesn't give the option of passing in a version AFAIK. For a large solution, the update can take a while, so there's no good way of knowing when it has completed.
One idea is that you could still use the Sleep method but with short time, for example, add a "for" loop for 10 times, just wart for 1000 ms every time, and use a method to check that whether one process like the VS windows was running using C# code(sample), at least, it will optimize the performance.
For the Nuget package update, the similar steps as above suggestion, you could get the package version number using C# code, just compare the version number.

Getting Process Desktop

As part of a project I'm making I create and use a new desktop by using CreateDesktop, SwitchDesktop, SetThreadDesktop... et al.
Once a new desktop is created and switched to a process is created via CreateProcess and the handle is stored so that when the desktop is closed I may terminate the process using TerminateProcess so that Windows can delete/close the desktop as all processes in a desktop must be terminated before the desktop can be closed.
However programs that use a file dialog open the user to executing arbitrary processes in the desktop, for example if I were to create a notepad.exe process for this desktop a user could click on File > Open navigate to %windir% and then execute explorer.exe which would actually open in the desktop giving it a taskbar, start button, and of course the ability to do anything they want.
The problem is that when something like this happens and the desktop is switched back to the main desktop my program only knows of and can terminate the processes it has started, leaving explorer.exe and any other processes left in the desktop to be in process limbo, preventing the desktop from being closed.
I was thinking about using EnumProcesses, then get a handle for each and hopefully finding a way to retrieve its desktop name so that I can compare it to mine and terminate the process.
Would this be the correct approach to this problem? If so what WinAPI function could I use to retrieve a process' desktop name? However if it is not the correct approach what can I do to fix, prevent, or mitigate this?
There are several approaches to solve your problem.
Like #eryksun said u can use EnumDesktopWindows to get all Top-Level windows and send them as WM_CLOSE Message. After that, you can use WaitForSingleObject or WaitForMultipleObjects with a timeout, so you can terminate them after the timeout. You can check the timeout with WAIT_TIMEOUT. Here is an example in python:
import win32api
import win32con
import win32event
import win32gui
import win32process
import win32service
def closeProcessesOnDesk(deskName,timeout):
print("try to close Processes on given Desktop")
hwndlist=[]
try:
hdesk = win32service.OpenDesktop(deskName,0,False,0)
hwndlist = hdesk.EnumDesktopWindows()
except Exception as ex:
print("Desktop wasn't found. {}".format(ex))
processlist =[]
#try to close nicely processes"
for hwnd in hwndlist:
# Get the window's process id's
if win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd):
tid, pid = win32process.GetWindowThreadProcessId(hwnd)
handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, 0, pid)
processlist.append(handle)
# Ask window nicely to close
try:
win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
except:
pass
# Allow some time for app to close
print("Start waiting for processes to close")
try:
state = win32event.WaitForMultipleObjects(processlist, True, timeout)
if state == win32con.WAIT_TIMEOUT:
print("processes didn't close nicely, terminate them")
except Exception as ex:
print("Error on closing Process. {}".format(ex))
finally:
for hproc in processlist:
if hproc:
win32api.TerminateProcess(hproc,0)
win32api.CloseHandle(hproc)
closeProcessesOnDesk('test',2000)
Since you have handle of your created Process, you can use it to terminate the whole processtree of this process. In your case every process which will be started with File > Open in notepad.exe will also be terminated. In python:
import subprocess
def killProcessTree(parent_pid):
try:
subprocess.call(['taskkill', '/F', '/T', '/PID', str(parent_pid)])
print("Process killed: %s " % str(parent_pid))
except Exception as ex:
print("Process was not killed %s" % ex
You can also create a new User with restricted rights so he is not even able to start another process. Use CreateProcessWithLogonW or CreateProcessAsUser to start the process as a "restricted User". But be careful, since winstations and Desktops are security objects you have to add the Users sid to the DACL of the desktop and winstation.

How to execute perl Win32::GuiTest functions when the user session is locked

I tried to execute the bellow perl script and locked the user session...
$n=15;
while($n>0)
{
print "$n,";
$n--;
sleep(1);
}
It worked as usual without any extra code..., There was no output when i locked the session, as i locked the session before the next second.
The output seen when I unlocked the session:
C:\Pradeep>perl test.pl
15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,
C:\Pradeep>
When i run the script bellow which I use to connect to a server using Win32::GuiTest functions like
SetForegroundWindow($_);
SendKeys("Password01");
etc...
it connected without any issues and the server login was successful.
But, when i lock my session in the middle of my execution and unlocked the session, the execution of script was completed, but the server login was not done.
use Win32::GuiTest qw(FindWindowLike GetWindowText SetForegroundWindow SendKeys);
system('"start %windir%\system32\mstsc.exe"');
$Win32::GuiTest::debug = 0;
$max_Sleep_time=3;
$Cur_Sleep_time=0;
do
{
sleep(1);
#windows = FindWindowLike(0, "Remote Desktop Connection");
$number_of_windows_opend = scalar(#windows);
$Cur_Sleep_time++;
}while ($number_of_windows_opend==0&&$Cur_Sleep_time!=$max_Sleep_time);
for (#windows) {
SetForegroundWindow($_);
SendKeys("server_name");
SendKeys("{ENTER}");
sleep(10);
#windows_seq = FindWindowLike(0, "Windows Security");
for (#windows_seq) {
SetForegroundWindow($_);
SendKeys("Password01");
SendKeys("{ENTER}");
}
#windows={};
exit;
}
According to me I used the active windows for doing my functionality. So it is not working.
is there any other way i can successfully do the above functionality if the user session is locked in the middle of the execution process. or do i have to make changes in my code?
Instead of using send keys use WMSetText(); function. It takes the window/control HWND and text as input and sets the text to the specified object.
Note: Using WMSetText(); you can just set the text, you can't send keys like {ENTER},{F1} etc...
You've already been told the answer several times:
http://perlmonks.org/?node_id=1073507
http://perlmonks.org/?node_id=1073302
http://perlmonks.org/?node_id=1073530
This is explained in the documentation of Win32::GuiTest. For obvious security reasons you can't send keys to applications when the screen is locked, you can't send keys to appications which aren't active.

Powershell 2 event handling

I'm trying to script Powerpoint with Powershell 2.0.
This site says there's a "PresentationOpen" event. However, Get-Member does not show this event. Also, when I try to do this:
register-objectevent $application PresentationOpen notification_event
it says: "Cannot register for event. An event with name 'PresentationOpen' does not exist."
Why is this event not accessible from PowerShell? Am I doing it wrong, and there is another way?
What I'm really trying to do is to wait until the presentation is fully loaded before I save it in another format. Not waiting causes PPT to freeze sometimes.
I'm grateful for any help!
PowerShell is pretty weak in COM support (it's a lot more like C# than it is like VB). In this case, you'll have to delegate the event. See the dispatches on this page: http://support.microsoft.com/kb/308825/EN-US/
There may be other (and better) ways to do this, but this should get you started:
$ppa = New-Object -ComObject PowerPoint.Application
$eventId = Register-ObjectEvent $ppa PresentationOpen -Action { "Hi" }
$ppa.Visible = 1
$ppa.Presentations.Open("Path\To\Presentation.ppt")
You would want to replace the script block after -Action on the second line with whatever code would do the processing/saving.
If there is any output from your event that you have registered, you can deal with it through the Receive-Job cmdlet, otherwise you can just simply add a loop similar to this right after the Open() method call to block further script execution until the slide deck has finished opening:
While ((Get-Job $eventId).State -neq "Completed") { Start-Sleep -m 250 }
Receive-Job $eventId

Resources