Wednesday, April 1, 2009

Remote WMI (System.Management) and Wake on LAN in an ASP.NET C#

Below is a solution I came up with for a request of an ASP.NET site that would allow a network admin to perform basic admin actions on AD Windows systems. Using the System.Management and System.Net.Sockets namespaces I was able to create a site that allows a user to power on, reboot, and query info (Drive Size, Processer info, User Logged On, etc...) from those systems. I like to refer to it as my poor man's version of SMS.

Reference Links for C# Remote Command Line and Wake on LAN:

http://www.dalun.com/blogs/05.09.2007.htm
http://www.codeproject.com/KB/IP/cswol.aspx


-------------RWMI.aspx.cs-------------------
using System;
using System.Collections.Generic;
using System.Collections;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Data;
using System.Text;
using System.IO;
using System.Management;

public partial class RWMI : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
//Load DataTable and GridView with Demo System Info...Configure Application
//to Pull Real Data from Either SQL or Local Text File
//Column 0 = Computer Name
//Column 1 = IP Address
//Column 2 = MAC Address (Format xx:xx:xx:xx:xx:xx)

DataTable dt = new DataTable();

dt.Columns.Add("system", typeof(System.String));
dt.Columns.Add("ipaddress", typeof(System.String));
dt.Columns.Add("macaddress", typeof(System.String));

DataRow dr = dt.NewRow();
dr[0] = "System One";
dr[1] = "192.168.1.100";
dr[2] = "00:1c:23:53:e4:38";
dt.Rows.Add(dr);

DataRow dr1 = dt.NewRow();
dr1[0] = "System Two";
dr1[1] = "192.168.1.101";
dr1[2] = "00:b0:d0:07:6f:e0";
dt.Rows.Add(dr1);

gvComputers.DataSource = dt;
gvComputers.DataBind();

}

protected void CheckStatus(object sender, GridViewRowEventArgs e)
{
//Upon Databound Event, Ping IP to See If It's Up...Disable Power On Button
//If Not Then Disable Restart and Info Buttons

if (e.Row.RowType == DataControlRowType.DataRow)
{
Button btn = new Button();
btn = (Button)e.Row.Cells[3].Controls[0];

Button btn1 = new Button();
btn1 = (Button)e.Row.Cells[4].Controls[0];

Button btn2 = new Button();
btn2 = (Button)e.Row.Cells[5].Controls[0];

Ping pingSender = new Ping();
PingOptions options = new PingOptions();
options.DontFragment = true;
string data = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
byte[] buffer = Encoding.ASCII.GetBytes(data);
int timeout = 120;
PingReply replyPing = pingSender.Send(e.Row.Cells[1].Text, timeout, buffer, options);

if (replyPing.Status != IPStatus.Success)
{
e.Row.CssClass = "NotGood";
btn.Enabled = true;
btn1.Enabled = false;
btn2.Enabled = false;
}
else
{
btn.Enabled = false;
btn1.Enabled = true;
btn2.Enabled = true;
}

}
}

protected void gvComputer_RowCommand(object sender, GridViewCommandEventArgs e)
{
try
{
//Finding Selected Row and Passing that Info to Function
int index = Convert.ToInt32(e.CommandArgument);
GridViewRow row = gvComputers.Rows[index];

//Creating Connection Options
ConnectionOptions coWMI = new ConnectionOptions();
//User Account Settings for Remote WMI Connection...Store Password in Web.Config for Better Security
coWMI.Username = "AdminUserID"; //AD Account That is an Admin on Local Systems
coWMI.Password = "AdminPassword"; //Password for that Account
coWMI.Authority = "NTLMDOMAIN:XXX"; //XXX is Your Domain

//Hide System Info Cell
tc2.Visible = false;

//Switch Statement for Button Command
switch (e.CommandName)
{
case "PowerOn":

TurnOnMAC(row.Cells[2].Text.ToString());
break;

case "Restart":

//Creates Remote Process on Selected System to Restart it in One Minute
ManagementScope msRP = new ManagementScope("\\\\" + row.Cells[1].Text.ToString() + "\\root\\cimv2", coWMI);
msRP.Connect();
ObjectGetOptions ogoRP = new ObjectGetOptions();
ManagementPath mpRP = new ManagementPath("Win32_Process");
ManagementClass mcRP = new ManagementClass(msRP, mpRP, ogoRP);
ManagementBaseObject inParams = mcRP.GetMethodParameters("Create");
inParams["CommandLine"] = @"shutdown /r /t 60";
ManagementBaseObject outParams = mcRP.InvokeMethod("Create", inParams, null);

break;

case "Info":

ArrayList alSoftware = new ArrayList();
ArrayList alDrives = new ArrayList();
ArrayList alProcess = new ArrayList();

//Setup Connection to Remote Systems root\cimv2
ManagementScope msWMI = new ManagementScope("\\\\" + row.Cells[1].Text.ToString() + "\\root\\cimv2", coWMI);
msWMI.Connect();

//WMI Query all Software Installed on System...Not Complete Listing...Only Software that writes to Certain Area in Registry
ObjectQuery oqSoftware = new ObjectQuery("Select Name from Win32_Product");
ManagementObjectSearcher mosSoftware = new ManagementObjectSearcher(msWMI, oqSoftware);
foreach (ManagementObject oReturn in mosSoftware.Get())
{
alSoftware.Add(oReturn["Name"].ToString());
}

alSoftware.Sort();
rptSoftware.DataSource = alSoftware.ToArray();
rptSoftware.DataBind();

//WMI Query for Computer Name, OS, RAM, and Last Bootup Time
ObjectQuery oqComputer = new ObjectQuery("Select * from Win32_OperatingSystem");
ManagementObjectSearcher mosComputer = new ManagementObjectSearcher(msWMI, oqComputer);
foreach (ManagementObject oReturn in mosComputer.Get())
{
lblComputerName.Text = oReturn["CSName"].ToString();
lblOS.Text = oReturn["Caption"].ToString() + " " + oReturn["CSDVersion"].ToString();
Decimal dRam = Convert.ToDecimal(oReturn["TotalVisibleMemorySize"].ToString());
dRam = dRam / 1000000M;
lblRam.Text = dRam.ToString("0.000") + " GBs";
lblBootTime.Text = ManagementDateTimeConverter.ToDateTime(oReturn["LastBootUpTime"].ToString()).ToString();
}

//WMI Query for Local Hard Drives...Calculate Free and Total Space
ObjectQuery oqDrives = new ObjectQuery("Select * from Win32_LogicalDisk WHERE DriveType=3");
ManagementObjectSearcher mosDrives = new ManagementObjectSearcher(msWMI, oqDrives);
foreach (ManagementObject oDrive in mosDrives.Get())
{
Decimal dFree = Convert.ToDecimal(oDrive["FreeSpace"].ToString());
dFree = dFree / 1073741824M;

Decimal dSize = Convert.ToDecimal(oDrive["Size"].ToString());
dSize = dSize / 1073741824M;

alDrives.Add("Drive: " + oDrive["DeviceID"].ToString() + " Free Space: " + dFree.ToString("0.00") + " GBs Total Size: " + dSize.ToString("0.00") + " GBs");

}
alDrives.Sort();
rptDisks.DataSource = alDrives.ToArray();
rptDisks.DataBind();

//WMI Query for Processor Information
ObjectQuery oqProcessor = new ObjectQuery("Select * from Win32_Processor");
ManagementObjectSearcher mosProcessor = new ManagementObjectSearcher(msWMI, oqProcessor);
foreach (ManagementObject oProcess in mosProcessor.Get())
{
alProcess.Add(oProcess["DeviceID"].ToString() + ": " + oProcess["Name"].ToString());

}
alProcess.Sort();
rptProcessor.DataSource = alProcess.ToArray();
rptProcessor.DataBind();

//WMI Query for Locally Logged On User...Will Display Admin Account If No One Logged On
ObjectQuery oqUsers = new ObjectQuery("Select * from Win32_ComputerSystem");
ManagementObjectSearcher mosUsers = new ManagementObjectSearcher(msWMI, oqUsers);
foreach (ManagementObject oUser in mosUsers.Get())
{
lblUser.Text = oUser["UserName"].ToString();
}


tc2.Visible = true;
break;

}
}
catch
{
Response.Write("Error Accessing System");
}
}

protected void TurnOnMAC(string macAddress)
{
//Wake On LAN...Take MAC Address (Format xx:xx:xx:xx:xx:xx)
//Convert to Byte...Send a UDP Packet to Wake Up System

UdpClient client = new UdpClient();
client.Connect(IPAddress.Broadcast, 40000);

Byte[] datagram = new byte[102];

for (int i = 0; i <= 5; i++)
{
datagram[i] = 0xff;
}


string[] macDigits = macAddress.Split(':');

for (int i = 1; i <= 16; i++)
{
for (int x = 0; x < 6; x++)
{
datagram[i * 6 + x] = (byte)Convert.ToInt32(macDigits[x], 16);
}
}

client.Send(datagram, datagram.Length);

}

}

--------------------Portion of RWMI.aspx--------------------------------

< p> < strong> Remote WMI (Windows Systems Only)< /strong> < /p>

< asp:Table ID="tb1" runat="server" CellSpacing="5">
< asp:TableRow>
< asp:TableCell ID="tc1" VerticalAlign="Top" runat="server">
< asp:GridView ID="gvComputers" runat="server" SelectedIndex="0" Font-Size="Small" AutoGenerateColumns="false" OnRowCommand="gvComputer_RowCommand" OnRowDataBound="CheckStatus" CellPadding="5" BorderWidth="2" GridLines="Both" >
< Columns>
< asp:BoundField DataField="system" HeaderText="Computer Name" />
< asp:BoundField DataField="ipaddress" HeaderText="IP Address" />
< asp:BoundField DataField="macaddress" HeaderText="MAC Address" />
< asp:buttonfield buttontype="Button" commandname="PowerOn" text="Power On"/>
< asp:buttonfield buttontype="Button" commandname="Restart" text="Restart"/>
< asp:buttonfield buttontype="Button" commandname="Info" text="Info"/>
< /Columns>
< /asp:GridView>
< /asp:TableCell>
< asp:TableCell ID="tc2" VerticalAlign="Top" Visible="false" runat="server">

< table cellpadding="5" cellspacing="2" border="2">
< tr> < td> < strong> Computer Info for < asp:Label ID="lblComputerName" runat="server" /> < /strong> < /td> < /tr>
< tr> < td> < strong> Logged On User:< /strong> < asp:Label ID="lblUser" runat="server" /> < /td> < /tr>
< tr> < td> < strong> Last Bootup Time:< /strong> < asp:Label ID="lblBootTime" runat="server" /> < /td> < /tr>
< tr> < td> < strong> OS:< /strong> < asp:Label ID="lblOS" runat="server" /> < /td> < /tr>
< tr> < td> < strong> RAM:< /strong> < asp:Label ID="lblRam" runat="server" /> < /td> < /tr>
< tr>
< td> < strong> Processor(s):< /strong> < br />

< asp:Repeater ID="rptProcessor" runat="server">
< ItemTemplate>
< %# Container.DataItem %>
< /ItemTemplate>
< SeparatorTemplate>
< br />
< /SeparatorTemplate>
< /asp:Repeater>
< /td>
< /tr>

< tr>
< td> < strong> Local Disk(s):< /strong> < br />

< asp:Repeater ID="rptDisks" runat="server">
< ItemTemplate>
< %# Container.DataItem %>
< /ItemTemplate>
< SeparatorTemplate>
< br />
< /SeparatorTemplate>
< /asp:Repeater>
< /td>
< /tr>

< tr>
< td> < strong> Installed Applications:< /strong> < br />

< asp:Repeater ID="rptSoftware" runat="server">
< ItemTemplate>
< %# Container.DataItem %>
< /ItemTemplate>
< SeparatorTemplate>
< br />
< /SeparatorTemplate>
< /asp:Repeater>
< /td>
< /tr>
< /table>
< /asp:TableCell>
< /asp:TableRow>
< /asp:Table>


Wednesday, March 11, 2009

Adobe Reader 9.1 .msi without Air.com

Found a way of creating an Adobe Reader 9.1 .msi that won't install Adobe Air.com

Here are the steps:

  1. Download the Adobe Reader 9.1 .exe installer from the Adobe FTP site


  2. Run this command:
    AdbeRdr910_en_US.exe -nos_o"Reader9" -nos_ne
    via the command line against the 9.1 installer to exact only the files and place them into a folder called Reader9 (very important see below)


  3. Using the Adobe Customization Wizard (available on the Adobe Enterprise Deployment site) configure a .mst using the .msi with the options you would like for the install


  4. Place the Reader9 folder (must be named that to not install Adobe Air.com) to a shared location and either configure a group policy or script to run the .msi

Friday, January 2, 2009

Configuring ASP.NET to use Integrated Security

Below are the steps for configuring Integrated Security for a ASP.NET application. These instructions are for Windows 2003 systems, one running IIS and the other SQL Server 2005.

  1. On the Web Server, Right Click My Computer and select Manage

  2. On the Computer Management Window, expand the Local Users and Groups menu item

  3. Right Click the Users folder and select New User

  4. On the New User window, enter the information for the local account. (Remember to uncheck the “User must change password at next logon” checkbox). Click
    Create

  5. Back on the Computer Management window, right click the local account and select
    Properties. On the Member of tab, click Add

  6. On the Select Groups window, ensure that the From this Location field is the name of the Web Server then click the
    Advanced button

  7. On the next window, click Find Now. Select the IIS_WPG group and then click
    OK. Click OK again to save the settings

  8. Grant the newly created local account Modify access to the C:\WINDOWS\Temp folder

  9. In IIS, expand the Application Pools menu. Either create a new application pool or right click an existing one. Select
    Properties and then the Identity tab

  10. Select Configurable then Browse for the newly created account and enter the password twice for the account. Click
    Apply and then OK

  11. On the Directory tab of the Properties for the Website, in the Application Pool field select it to run using the application pool identified with the local account

  12. Repeat steps 1 through 4 to create a local account with the same User ID,Name, and Password on the SQL Server. This local account doesn’t need to a be a member of any groups

  13. Open SQL Server Management Studio

  14. Expand the Security menu for the server

  15. Right Click the Logins folder and select New Login

  16. Click the Search button and find the local account on the SQL server

  17. Map that account to the required database

  18. Grant the local account access to any tables or stored procedures


Saturday, December 27, 2008

Bare Metal Restore of Windows 2008 Server from Remote WBADMIN Backups

As most users of Windows 2008 Server know you can easily use the WBADMIN command as a backup solution. WBADMIN allows to use remote shares as storage locations for backup sets. Below are the steps to perform a bare metal restore using a backup sets located on a remote share. In order for this to work you will need to have a DHCP server running on your network that will give out an IP address to the host being recovered.



  1. Boot from the Windows 2008 CD. Click Next

  2. On the Install Now Window, Click the Repair Your Computer link

  3. Click Next again to move to the next window

  4. Choose Windows Complete PC Restore

  5. When the error message about not finding a valid backup comes up, click Cancel

  6. On the Restore Your Entire Computer for a Backup window, select Restore a Different Backup and click Next

  7. On the Select the Location of the Backup window, click the Advanced button

  8. Click Search for a Backup on the Network

  9. Click Yes when prompted

  10. Type in the network location of the backup and click OK

  11. Enter in your AD Admin account credentials (domain\userid) when connecting to server hosting the backup sets and click
    OK

  12. Highlight the backup set location for the computer you want restored and clickNext


  13. Highlight the backup point you want to recover and click Next

  14. Click Next. Depending upon the type of restore you might have to check the "Format and repartition disks" checkbox


  15. On the Summary page, click Finish


  16. Check the I confirm that I want to format the disks and restore the backup checkbox. Then click
    OK to start the recovery process



Monday, December 15, 2008

VBScript: Preventing Logon After Hours

Last week I received a request asking if I could prevent a certain user from logging into a system after normal business hours.

I thought it would be easy using AD and just setting the login hours for her account; however, since the user's email is routed to my Exchange server, it caused her to be locked out of her email after hours. The solution I came up with is a simple VBScript.

The script checks the day and time. If they are out of the acceptable range then using the shutdown command I reboot the box in 60 seconds. Both the system and the script will present a popup window notifying the user of the situation. I configured a group policy to run the script at logon and only for a specific AD group (which the user is a member of).

So that covers logging in but what if the user is already logged onto the system. Another group policy, with a preference setting for a scheduled task running the shutdown /r /t 60 command at 6 PM everyday does the trick.


'************************************************************************

on error resume next

dim vday, vhour

vday = weekday(now)
vhour = hour(now)

set wshshell = wscript.createobject("wscript.shell")

if vday >= 2 and vday <= 6 then

if vhour < 8 or vhour > 17 then

wshshell.run "C:\WINDOWS\system32\shutdown.exe /r /t 60"
wshshell.popup "Your Account is Only Permitted to Login Between 8AM" _
& " and 6PM" & vbCrLf & "Monday through Friday", 20, "Account Logoff"

end if

else

wshshell.run "C:\WINDOWS\system32\shutdown.exe /r /t 60"
wshshell.popup "Your Account is Only Permitted to Login Between 8AM" _
& " and 6PM" & vbCrLf & "Monday through Friday", 20, "Account Logoff"

end if

wscript.quit

'**************************************************************************************************************

Wednesday, September 24, 2008

Perl Script to Update Recommended IP Block Ranges

Yesterday, I started down the road of learning to develop in Perl. Came up with the idea of updating my OpenBSD firewall badhosts table with the DShields recommended IP block range list.

In order to get it to run I had to install the p5-LWP-UserAgent-Determined-1.03.tgz package on the OpenBSD system.

Configured Cron to run the script:
sudo crontab -e
* 23 * * * /usr/bin/perl /etc/bhupdate.pl >/dev/null 2>&1

----Part of pf.conf-------------

table <badhosts> persist file "/etc/badhosts"

block in log quick on $ext_if from <badhosts> \
label "Badhosts in"
block out log quick on $ext_if to <badhosts> \
label "Badhosts out"

--------------------------------------------------

Here is the Perl script:

#!/usr/local/bin/perl -w
use LWP::Simple;

#open the badhosts file and load it to an array
open(BH,"/etc/badhosts");
@badhosts = ;
close BH;

#create arrays and get recommended block data from site
@badips = ();
@dshield = split("\n",get('http://feeds.dshield.org/block.txt'));

foreach $newrange(@dshield)
{
#check to see if line starts with an ip. if so then
#pull only the first ip

if($newrange =~ m/^\d/i)
{
@ipinfo = split("\t",$newrange);
$ip = "$ipinfo[0]/24\n";

$counter = 0;

#check to see if ip range is already listed in badhosts file
#if not then load in into badips array

foreach $badrange(@badhosts)
{
if($badrange eq $ip)
{
$counter++;
}
}

if($counter == 0)
{
push(@badips,$ip);
}

}

}

#append badhosts file with newly recommended block ranges
open(BH,">>/etc/badhosts");
print BH @badips;
close BH;

system("pfctl -f /etc/pf.conf")

Friday, September 12, 2008

Quick Way to Change File Extensions in the Same Folder

I was given the task of searching a few hundred archived Eudora mailbox files today for a certain email address. Didn't want to install Eudora on a system so I just opened the files with Notepad. The problem I ran into was that Windows Search wouldn't search the .mbx files.

So I needed to quickly change all the .mbx files in the folder to .txt extension so that Windows Search could scan them. Thought VBScript would be a good way; however, found that it would take too much code to just do a simple task.

Instead I went back to the command line and used the following command on the folder:

ren *.mbx *.txt

This allowed me to quickly find the requested email data using Windows Search.