I've built a monitoring application and one of the many things it will monitor are a few services on a few different servers in our network and I'd like to display the status of these services and whether they are still working or not.
So, lets say I'd want to run this short Powershell script;
Get-Service -ComputerName "DESKTOP" -Name "WinDefend"
And let's say I'd like to run this every 1 minute using the Timer event. Which would look something like this;
private void InitializeTimer()
{
// Call this procedure when the application starts
timer1.Interval = 60000; // 60 seconds
timer1.Tick += new EventHandler(timer1_Tick);
// Enable timer
timer1.Enabled = true;
}
// Timer tick
private void timer1_Tick(object sender, EventArgs e)
{
// Powershell script here
}
How would I actually implement this short Powershell script into this example?
My second question would also be, how could I correctly display the data after retreiving it? Could I somehow Write the data to maybe a text box or Label?
Thank you very much for any feedback and/ or help!
C# WinForms Calling PowerShell 5.1
Problems to Overcome:
To run PowerShell version 5.1 in C#, I believe you need your C# program to be based on .NET Framework, probably 4.x. This may not be true, but I've already spent far more time on this than I ever expected, so not going to investigate alternatives.
C# used to offer through Nuget an easy method for adding PowerShell 5.1 to your project. This is no longer true and you now have to take extra steps to obtain a version of System.Management.Automation.dll that is designed for PowerShell 5.1.
While you can, with extra steps, obtains System.Management.Automation.dll from NuGet, it is not as new as the version used by PowerShell 5.1 on a fully updated Windows 10 system.
The version of Get-Service provided by versions of PowerShell 7.x, and I believe 6.x as well, do NOT provide the ComputerName parameter, nor any built in method for accessing remote computers. There are options for executing Get-Service on a remote system and returning the results, but this too can be problematic - as in firewall issues, certain service(s) not running, I'm thinking certain registry key setting can cause issues, etc...
The Forms.Timer class can update controls on a form due to the fact that it uses the same thread, BUT, this means that waiting 20 seconds for Get-Service to return info from a remote computer will lock that thread and freeze the form.
Building VS C# project with correct version of System.Management.Automation.dll:
Some generalized steps:
Start Visual Studio 2022:
"Create a new project"
C# -> Windows -> Desktop
Windows Forms App (.NET Framework) -> Next
Set "Project name" to project's name
Set "Location" to project's path
Set "Framework" to ".NET Framework 4.8"
"Create"
Working in the Visual Studio 2022 Project:
Tools -> NuGet Package Manager -> Package Manager Console
Enter command to add System.Management.Automation.dll
Get command from here: https://www.nuget.org/packages/System.Management.Automation.dll/
NuGet command: NuGet\Install-Package System.Management.Automation.dll -Version 10.0.10586
Optional:
In PowerShell 5.1
Navigate to the project's \packages\System.Management.Automation.dll.10.0.10586.0\lib\net40
Execute: Copy ([PSObject].Assembly.Location)
The trick to copy System.Management.Automation.dll from Windows' PowerShell 5.1 came from here (Other useful info on the same page):
https://stackoverflow.com/a/13485939/4190564
When designing the form and controls, I believe this is all of the unique settings that I used:
Form:
(Name) = runPowerShellForm
Size = 700, 200
Text = "Test Form for Running PowerShell"
{Set Load to run RunPS}
Label:
(Name) = outputLabel
AutoSize = false
Anchor = Top, Bottom, Left, Right
BackColor = White
Font = Lucida Console, 12pt
ForeColor = Navy
Size = 660, 114
Text = ""
TextBox:
(Name) = computerNameTextBox
Anchor = Bottom, Left, Right
Size = 532, 20
Button:
(Name) = updateComputerButton
Anchor = Bottom, Right
Size = 122, 23
Text = "Update Computer"
{Set button click}
And this is the actual code:
public partial class runPowerShellForm : Form {
private static string _computerName = ".";
private static int _tickCount = 0;
private static System.Timers.Timer _timer = new System.Timers.Timer();
private static Label _outputLabel = null;
private static PowerShell ps = PowerShell.Create();
private static void NewGetService(string computerName) {
_computerName = computerName;
ps = PowerShell.Create();
ps.AddCommand("Get-Service").AddParameter("ComputerName", computerName).AddParameter("Name", "WinDefend");
}
private static void RunPS() {
string LabelText = "Computer: " + _computerName + "\r\nTick Count: " + (++_tickCount).ToString() + "\r\n\r\n";
LabelText += "Status Name DisplayName\r\n";
LabelText += "------ ---- -----------\r\n";
foreach(PSObject result in ps.Invoke()) {
LabelText += String.Format(
"{0,-9}{1,-19}{2}",
result.Members["Status"].Value,
result.Members["Name"].Value,
result.Members["DisplayName"].Value);
}
_outputLabel.BeginInvoke(new UpdateLabel(UpdateMethod), _outputLabel, LabelText);
}
public delegate void UpdateLabel(Label arg1, string arg2);
public static void UpdateMethod(Label labelCtrl, string textStr) {
labelCtrl.Text = textStr;
}
private static void OnTickEvent(Object source, System.Timers.ElapsedEventArgs e) {
RunPS();
}
public runPowerShellForm() {
InitializeComponent();
}
private void updateComputerButton_Click(object sender, EventArgs e) {
NewGetService(this.computerNameTextBox.Text);
this.computerNameTextBox.Text = "";
RunPS();
}
private void runPowerShellForm_Load(object sender, EventArgs e) {
_outputLabel = this.outputLabel;
_timer.Elapsed += OnTickEvent;
_timer.Interval = 30000;
_timer.Enabled = true;
NewGetService(_computerName);
RunPS();
}
private void runPowerShellForm_SizeChanged(object sender, EventArgs e) {
this.computerNameTextBox.Text = this.Size.ToString();
}
}
The Original Answer:
This falls far short of answering the question, but it does illustrate how to call PowerShell from the C# that is called from PowerShell.
If you are trying to execute a single PowerShell command, then maybe the answers to Run PSCmdLets in C# code (Citrix XenDesktop) and How to execute a powershell script using c# and setting execution policy? would do what you want.
I'm doing everything in PowerShell right now, so just easier for me to create a PowerShell script that calls C# that Calls PowerShell. This PowerShell script calls the Run method of the C# class DoPS that invokes Get-Service -ComputerName "." -Name "WinDefend" and then uses WriteLine statements to mimic the expected output of Get-Service:
Add-Type -Language 'CSharp' -TypeDefinition #'
using System;
using System.Management.Automation;
public class DoPS {
public void Run(){
//Get-Service -ComputerName "." -Name "WinDefend"
PowerShell ps = PowerShell.Create();
ps.AddCommand("Get-Service").AddParameter("ComputerName", ".").AddParameter("Name", "WinDefend");
Console.WriteLine("Status Name DisplayName");
Console.WriteLine("------ ---- -----------");
foreach (PSObject result in ps.Invoke()) {
Console.WriteLine(
"{0,-9}{1,-19}{2}",
result.Members["Status"].Value,
result.Members["Name"].Value,
result.Members["DisplayName"].Value);
}
}
}
'#
$DoPS = [DoPS]::new()
$DoPS.Run()
Which outputs this text:
Status Name DisplayName
------ ---- -----------
Running WinDefend Microsoft Defender Antivirus Service
I want to set up a WiFiDirect connect between 2 windows 10 in a console application not in a universal app.
I set the target platform version to 10
<TargetPlatformVersion>10.0</TargetPlatformVersion>
I add the Reference to Windows.Foundation, Windows.Networking.Proximity, Windows.Networking.Sockets, Windows.Storage.Streams
In my application i check if WiFiDirect is suppartet.
if ((Windows.Networking.Proximity.PeerFinder.SupportedDiscoveryTypes & Windows.Networking.Proximity.PeerDiscoveryTypes.Browse) != Windows.Networking.Proximity.PeerDiscoveryTypes.Browse)
{
Console.WriteLine("Peer discovery using Wifi-Direct is not supported.\n");
}
This works as it should.
But when i call PeerFinder.Start() i get a exception .
HRESULT 0x80004004(E_ABORT)
This is the code calling PeerFinder.Start()
try
{
PeerFinder.AllowWiFiDirect = true;
PeerFinder.Role = PeerRole.Peer;
PeerFinder.ConnectionRequested += PeerFinder_ConnectionRequested;
PeerFinder.TriggeredConnectionStateChanged += PeerFinder_TriggeredConnectionStateChanged;
using (var discoveryDataWriter = new Windows.Storage.Streams.DataWriter(new Windows.Storage.Streams.InMemoryRandomAccessStream()))
{
discoveryDataWriter.WriteString("test12");
PeerFinder.DiscoveryData = discoveryDataWriter.DetachBuffer();
}
PeerFinder.Start();
}
catch (Exception e)
{
Console.WriteLine("start failed: " + e.Message);
}
i guess i can't use PeerFinder in a console application, are there any alternatives to establish a WiFiDirect connection for desktop applications?
I am deploying a C# application on client machine. The Application need to access code from another program, so it can scrap text from the screen of another application. It is running fine on the development machine but on the client machine it is throwing an error "ActiveX Component cannot create Object" this is where i am getting the error from!
private ExtraSession objExtraSession;
private ExtraSessions objExtraSessions;
private ExtraScreen objExtraScreen;
private ExtraArea objExtraArea;
private ExtraSystem objExtraSystem;
protected void sessionInitializer()
{
try
{
objExtraSystem = (ExtraSystem) Microsoft.VisualBasic.Interaction.CreateObject("Extra.system");
if (objExtraSystem == null)
{
MessageBox.Show("Could not create system");
return;
}
objExtraSessions = objExtraSystem.Sessions;
if (objExtraSessions == null)
{
MessageBox.Show("Could not create sessions");
return;
}
if (!System.IO.File.Exists("C:\\Users\\" + userid + "\\Documents\\Attachmate\\EXTRA!\\Sessions\\SAS.edp"))
{
MessageBox.Show("File does not exist");
return;
}
objExtraSession = (ExtraSession) Microsoft.VisualBasic.Interaction.GetObject("C:\\Users\\"+ userid + "\\Documents\\Attachmate\\EXTRA!\\Sessions\\SAS.edp");
if (objExtraSession == null)
{
MessageBox.Show("Could not create session");
return;
}
if (objExtraSession.Visible == 0)
{
objExtraSession.Visible = 1;
}
objExtraScreen = objExtraSession.Screen;
}
catch (Exception ex)
{
MessageBox.Show(ex.StackTrace, "Failed to initialize Attachmate sessions");
}
}
The error is generated from objExtraSession = (ExtraSession) Microsoft.VisualBasic.Interaction.GetObject("C:\Users\"+ userid + "\Documents\Attachmate\EXTRA!\Sessions\SAS.edp");
Am I missing some step. Please help me out. Thanks in advance.
The most likely explanation is that your development machine has the ActiveX control installed, but the client machine does not. Read the deployment documentation for the control and do what is says is required to deploy to the client machine.
Thanks for all your responses... The method GetObject was creating an object whose activex component was not registered... I resolved by finding the corresponding *.ocx file and calling Regsvr32 on the file this resolved the problem...
I am using WATIN 2.1 with C#. Here is my Code
// Should I add something here like []
private void ProcessInkPresenter()
{
String path = "http://localhost/index.asp?HOSTID=AD&USERID=&ALIAS=" + userName;
Int32 startingRow = 1;
using (var browser = new IE(path))
{
browser.AutoClose = true;
try
{
try
{
browser.Image(Find.ByAlt("Use a password")).Click();
browser.WaitForComplete(90);
browser.TextField(Find.ByName("_MYPW")).TypeText(privateCurrentPassword);
// the application keeps crashing in the line above
// WatiN.Core.Exceptions.RunScriptException : RunScript failed
// ----> System.UnauthorizedAccessException : Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
browser.Image(Find.ByAlt("Verify password")).Click();
browser.Link(Find.ByTitle("Change passwords")).Click();
browser.TextField(Find.ByName("_NEWP1")).TypeText(privateNewPassword);
browser.TextField(Find.ByName("_NEWP2")).TypeText(privateNewPassword);
browser.Image(Find.ByName("SUBMIT-CHANGE")).Click();
I basically need to get this to work with Internet Explorer 7 and up on Windows XP.
Can you please help me with this?
I added the CRASH details in the code above
Thanks
Is the field found?
var field = browser.TextField(Find.ByName("_MYPW"));
if(field.Exists)
field.TypeText(privateCurrentPassword);
I had a similar issue trying to get an HTML5 input type email.
It could also be other reasons watin can't get to the field.
I am using SharePoint Server 2007 Enterprise with Windows Server 2008 Enterprise. I have deployed a publishing portal. I am developing a ASP.Net web application using VSTS 2008 + C# + .Net 3.5 + ASP.Net + SharePoint Server 2007 SDK.
Here is my code snippets and I got error -- "Updates are currently disallowed on GET requests". Any ideas how to fix?
Microsoft.SharePoint.SPException: Updates are currently disallowed on GET requests. To allow updates on a GET, set the 'AllowUnsafeUpdates' property on SPWeb.
protected void Page_Load(object sender, EventArgs e)
{
try
{
SPWebApplication webApp = SPContext.Current.Site.WebApplication;
SPSiteCollection siteCollections = webApp.Sites;
foreach (SPSite item in siteCollections)
{
SPWeb webItem = item.OpenWeb();
webItem.AllowUnsafeUpdates = true;
}
SPSite newSiteCollection = siteCollections.Add("sites/myblog",
"New Sample Site", "New Sample Site Description", 1033, "BLOG#0",
"foo\\foouser", "Owner name", "owneremail#foo.com");
}
catch (Exception ex)
{
Response.Write(ex.ToString() + "\t" + ex.StackTrace);
}
}
The problem why you are not allowed to read/write to database on GET request is because your code will be exploitable via a cross-site scripting. Read about AllowUnsafeUpdates consequences here.
Anyway, if you like, you can set this SPWeb.AllowUnsafeUpdates to true, but use it like this:
try {
web.AllowUnsafeUpdates = true;
...
}
finally {
web.AllowUnsafeUpdates = false;
}
Add a check to ensure that you are getting a POST instead of a GET before you attempt to allow updates. Make sure that whatever is making the change does it via a POST request rather than using URL parameters and a GET.
if (IsPostBack)
{
...
}