We are designing a monitoring solution for our system, and we're looking into WMI as a possible option.
In short, we want to create a generic system where it shall be possible to subscribe to multiple changes in WMI data instances. We're looking into the __InstanceModificationEvent to do this:
The following prototype code monitors all changes on any instance of notepad:
void StartMonitor()
{
var query = "SELECT * "
+ "FROM __InstanceModificationEvent "
+ "WITHIN 1 "
+ "WHERE TargetInstance Isa \"Win32_PerfFormattedData_PerfProc_Process\" "
+ "AND TargetInstance.Name = \"notepad\"";
var scope = new ManagementScope(#"root\cimv2", null);
scope.Connect();
EventQuery qry = new EventQuery(query);
ManagementEventWatcher w = new ManagementEventWatcher(scope, qry);
w.EventArrived += EventArrived;
w.Start();
}
void EventArrived(object sender, EventArrivedEventArgs e)
{
var targetInstance = (ManagementBaseObject)e.NewEvent["TargetInstance"];
foreach (var p in targetInstance.Properties)
{
Console.WriteLine(p.Name + ":\t" + (p.Value != null ? p.Value.ToString() : "{null}"));
}
}
So whenever any instance of notepad is changes, we will get an output like this (excerpt)
PageFileBytes: 1499136
PageFileBytesPeak: 1740800
PercentPrivilegedTime: 0
PercentProcessorTime: 0
PercentUserTime: 0
PoolNonpagedBytes: 7040
PoolPagedBytes: 172856
This is fine, however we need to improve this a bit. For example, let's pretend we are only interested in the changes to PercentProcessorTime. With the current code, the event will be fired whenever anything in the object changes. This is not good enough, because we might monitor hundreds of processes across multiple computers.
Thus, we need a way to specify that we only want the event to be triggered whenever this or that property changes, not the entire instance
Is this possible using WMI? What's the best practice to achieve what we want?
Edit: I know that it is possible to write a query such as the one below and cycically poll for the value, however we were hoping to avoid that approach.
SELECT PercentProcessorTime
FROM Win32_PerfFormattedData_PerfProc_Process
WHERE Name = "notepad"
The __InstanceModificationEvent has a reference to the previous instance so you might be able to compare values between the PreviousInstance and TargetInstance. For example, to filter for PercentProcessorTime changes:
var query = "SELECT * "
+ "FROM __InstanceModificationEvent "
+ "WITHIN 1 "
+ "WHERE TargetInstance Isa \"Win32_PerfFormattedData_PerfProc_Process\" "
+ "AND TargetInstance.Name = \"notepad\" "
+ "AND PreviousInstance.PercentProcessorTime != TargetInstance.PercentProcessorTime ";
Related
I have a utility that generates query strings, but the static code analyzers (and my coworkers) are complaining because of risk of "SQL Injection Attack".
Here is my C# code
public static string[] GenerateQueries(string TableName, string ColumnName)
{
return new string[] {
"SELECT * FROM " + TableName,
"SELECT * FROM " + TableName + " WHERE 1=2",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = #id",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = IDENT_CURRENT('" + TableName + "')",
"SELECT * FROM " + TableName + " WHERE [" + ColumnName + "] = #value"
};
}
In the code I always call it only with constant strings, such as
var queryList = GenerateQueries("Person", "Name");
Is there any way to rewrite this function so that it is "safe"? For example, if I were using C instead of C#, I could write a macro to generate the strings safely.
At the moment, the only choice I have is to copy/paste this block of SELECT statements for every single table, which is ugly and a maintenance burden.
Is copy/paste really my only option?
EDIT:
Thank you for the replies, esp William Leader. Now I see that my question is wrong-headed. It isn't just the fact that I am concatenating query strings, but also storing them in a variable. The only proper way to do this is to construct the SqlDataAdapter using a constant such as,
var adapter = new SqlDataAdapter("SELECT * FROM PERSON");
There is no other choice. So yes, there will be a lot of copy/paste. I'm starting to regret not using EF.
I was shocked at first, but on reflection this is no different than having an SQL statement already in your code that looks like this:
"SELECT * FROM Person"
We do that kind of thing all the time.
IF
There's an important caveat here. That only remains true if you can control how the function is called. So if this method is a private member of a data layer class somewhere, you might be okay. But I also wonder how useful this really is. It seems like you're not saving much over what you'd get from just writing the queries.
Additionally, it's not good to be in the habit of ignoring your static analysis tools. Sometimes they give you stuff you just know is wrong, but you change it anyway so that when they do find something important you're not conditioned to ignore it.
What your Code analyser is telling you is that you should most likely be calling a procedure with some parameters instead of sending SQL across the wire.
It does not mater a single bit whether or not you use a macro to generate your SQL statements, if you are sending raw SQL across the wire you are open to SQL Injection Attacks
Sending SQL commands to an endpoint making a non sanctioned call. If we fire up a network packet sniffer, we can see that you have a database configured to allow SQL commands to be sent, so we can inject illegal SQL into the system
You could still rely on a single procedure for calling your updates, but if you elect to move to procedures, why would you want to do that?
EDITED to provide an example
create PROC sp_CommonSelectFromTableProc #tableName varchar(32)
AS
-- code to check the tableName parameter does not contain SQL and/or is a valid tableName
-- your procedure code here will probable use
-- exec mydynamicSQLString
-- where mydynamicSQLString is constructed using #tableName
END;
or maybe a table specific procedure
create PROC sp_SelectFromSpecificTableProc
AS
SELECT * FROM SpecificTable
END;
What is important to remember is that SQL injection is independent of the technology used for the underlying application.
It is just overt when the application contains such constructs as
return new string[] {
"SELECT * FROM " + TableName,
"SELECT * FROM " + TableName + " WHERE 1=2",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = #id",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = IDENT_CURRENT('" + TableName + "')",
"SELECT * FROM " + TableName + " WHERE [" + ColumnName + "] = #value"
SQL Injection must be addressed at both ends of the data channel.
Here is a pretty good starting point for understanding how to mitigate for SQL Injection attacks
I'm trying to build a query, to list all the known computers in SCCM with a specific name.
The query looks like this:
string query = string.Format("Select Name From SMS_R_System Where Name like '" + "%" + computerName + "%" + "'");
If results are found, it puts the result(s) in a dropdown box.
My problem in these case, the output looks like this:
"instance of SMS_R_System{Name = "DC01";};"
But of course, for our use case we only need DC01 as output.
Any tips?
The full Code for the ButtonEvent:
private void ChkBtn_Click(object sender, EventArgs e)
{
string computerName = PCDropDown.Text;
lBox.Items.Clear();
SmsNamedValuesDictionary namedValues = new SmsNamedValuesDictionary();
WqlConnectionManager connection = new WqlConnectionManager(namedValues);
// Connect to remote computer.
try
{
connection.Connect(PrimarySiteServer.ToString());
// Set the query.
string query1 = string.Format("Select Name From SMS_R_System Where Name like '" + "%" + computerName + "%" + "'");
string query2 = string.Format("Select * From SMS_UserMachineRelationship WHERE ResourceName like '" + "%" + computerName + "%" + "' AND IsActive = '1' AND Types = '1'");
// Get the query results
IResultObject queryResults = connection.QueryProcessor.ExecuteQuery(query1);
// Check for results and display in infobox
bool resultsFound = false;
foreach (IResultObject queryResult in queryResults)
{
resultsFound = true;
lBox.Items.Add("Rechner ist vorhanden");
PCDropDown.Items.Add(queryResult.ToString());
}
if (resultsFound == false)
{
lBox.Items.Add("Rechnername nicht gefunden");
}
}
catch
{
MessageBox.Show("No Connection to Config-Manager - Als ZZA ausgeführt? SCCM-Servername richtig?");
}
}
Instead of adding queryResult.ToString() like you do here:
PCDropDown.Items.Add(queryResult.ToString());
you need to add the correct field of queryResult, so in this case:
PCDropDown.Items.Add(queryResult["Name"].StringValue);
Also a quick note. I don't know for who you are writing this and what the next step would be but if this is a read only application that is only used by SCCM Admins I would consider ignoring WMI and going to the SCCM DB via SQL instead. It is a lot faster, SQL has far more powerful options for queries and it does not need the integration of those strange sccm console Dlls (although that is not 100% necessary for WMI either).
If you need write access to create devices or collections etc., or you need to work with the roles the sccm access rights systems implements however WMI is the better or only choice. (And in this case I'd rather really use those strange dlls because all of the MS examples rely on them and it can be hard to translate those tutorials to the vanilla WMI solution C# offers.
I am building an intranet for my school project inside a form application using the .NET framework and the C# language. This function populates a data grid inside a form. However, the code inside the while structure won't run. I have gone step by step with the debugger, and I reckon the read() method of the "dr" object will not go trough my two inner joined tables, but when I press a button that i have set to show me how many books are there registered in the database, it works. I think there is something wrong with my SQL statement, by what I have searched on this site, but I did not manage to resolve the bug. Carti.Nota and Carti.Stoc are columns with the Number type inside a Microsoft Access 2013 Database.
private void PopulateGridBooks()
{
dataGridView1.Rows.Clear();
using (OleDbConnection connect = new OleDbConnection(ConfigurationManager.ConnectionStrings["LibrarieConectare"].ConnectionString))
{
OleDbCommand command = connect.CreateCommand();
command.Parameters.AddWithValue("#titlu", cautaTitlu.Text);
command.CommandText =
" SELECT Carti.IDCarte, Carti.Titlu, Carti.Editie, Carti.An, Carti.ISBN, Carti.Nota, Carti.IDAutor, Carti.Stoc, Edituri.NumeEditura " +
" FROM (Carti INNER JOIN Edituri ON Carti.IDEditura = Edituri.IDEditura) " +
" WHERE Titlu LIKE '%#titlu%'";
try
{
connect.Open();
OleDbDataReader dr = command.ExecuteReader();
if (dr.HasRows)
{
while (dr.Read())
{
dataGridView1.Rows.Add(dr["IDCarte"], dr["Titlu"], dr["NumeEditura"], dr["Stoc"]);
}
}
}
catch (Exception ex)
{
MessageBox.Show("Eroare la populare data grid carti: " + ex.Message);
}
}
}
Think I found it - your parameter is wrong, you should not have an # in the AddwithValue.
Use:
//Exclude the # in the below.
command.Parameters.AddWithValue("titlu", cautaTitlu.Text);
maybe move your parameter addition line to after the command text too.
Try this,
command.CommandText =
" SELECT Carti.IDCarte, Carti.Titlu, Carti.Editie, Carti.An, Carti.ISBN, Carti.Nota, Carti.IDAutor, Carti.Stoc, Edituri.NumeEditura " +
" FROM Carti INNER JOIN Edituri ON Carti.IDEditura = Edituri.IDEditura " +
" WHERE Titlu LIKE '%' + #titlu + '%';
I suggest change in where clause as the following:
"WHERE Titlu LIKE '%' + #titlu + '%'"
I can insert my XML easily into my database table, but i follow this exhausting manner as seen in my down code using LINK, which is perfectly tested. But, I wonder if I can find a way to read all my XML descendants elements of "Level" node, using iteration of all child tagnames, because when I make any change to my XML file, I would have to change my LINK code once again,and usually i'll face some errors when i use this exhausting manner. Please help improve this code:
try
{
XElement d = XElement.Parse(richTextBox1.Text.ToString());
var people = (from Level in d.Descendants("Level")
select new
{
ID = Convert.ToInt32(Level.Element("ID").Value),
Day1 = Level.Element("Day1").Value,
Day2 = Level.Element("Day2").Value,
Day3 = Level.Element("Day3").Value,
Day4 = Level.Element("Day4").Value,
Day5 = Level.Element("Day5").Value,
Day6 = Level.Element("Day6").Value,
Day7 = Level.Element("Day7").Value
}).ToList();
foreach (var item in people)
{
//Insert and Update
datacommand1.CommandText = "Insert Into MyTable(ID,Day1,Day2,Day3,Day4,Day5,Day6,Day7) values(" + item.ID + "," + "','" + item.Day1 + "','" + item.Day2 + "','" + item.Day3 + "','" + item.Day4 + "','" + item.Day5 + "','" + item.Day6 + "','" + item.Day7 + "')";
datacommand1.ExecuteNonQuery();
}
}
my XML file seems like that:
<level>
<id> 101 </id>
<Day1> task 1</Day1>
<Day2> task 2</Day2>
<Day3> task 3</Day3>
<Day4> task 4</Day4>
<Day5> task 5</Day5>
<Day6> task 6</Day6>
<Day7> task7 </Day7>
</level>
Instead of declaring one variable for each tag in the file, you may want to try to iterate over all them, extracting the name and the value for each one and composing the SQL statement at runtime from those, no matter what they are. Try something like this:
IEnumerable<XElement> items = d.Descendants("level").Elements();
string names = string.Empty;
string values = string.Empty;
foreach (XElement item in items)
{
names += item.Name + ",";
values += "#" + item.Name + ",";
IDbDataParameter parameter = datacommand1.CreateParameter();
parameter.ParameterName = "#" + item.Name;
parameter.DbType = DbType.String;
parameter.Value = item.Value;
datacommand1.Parameters.Add(parameter);
}
datacommand1.CommandText = "INSERT INTO MyTable (" + names.Substring(names.Length - 1) + ") VALUES (" + values.Substring(values.Length - 1) + ");";
datacommand1.ExecuteNonQuery();
This builds the command on the fly, based on the XML structure, and fills its parameters with the data there. But doing so relies on the fact that the table structure will be exactly the same as in the file, and still needs manual schema updating when the structure changes, but as long as they're in sync it should be fine.
EDIT
About datatypes, there are 2 choices I can think of. Leave as it is, sending everything as strings no matter what, and rely on the DB engine to parse, validate and turn those into numbers (depending on what DB you're using, that may be possible or not, but I guess it's not rare to see). Or modify the code to separate the special fields apart from the loop (and excluding from it) and add those one by one, specifying their types accordingly. This is easy to do if the numeric columns are fixed, like the ID, and everything else is text.
It was easy to build a custom query like this with ADO.NET:
SqlCommand.CommandText = "SELECT Column" + variable1 + ", Column" + Variable2 + " FROM TABLE";
Is that able to do so in LINQ to SQL?
Thanks
No there is no common way to build a dynamic query.
A method has to have a known, specific return type. That type can be
System.Object but then you have to use a lot of ugly reflection code
to actually get the members. And in this case you'd also have to use a
lot of ugly reflection expression tree code to generate the return
value.
If you're trying to dynamically generate the columns on the UI side -
stop doing that. Define the columns at design time, then simply
show/hide the columns you actually need/want the user to see. Have
your query return all of the columns that might be visible.
Unless you're noticing a serious performance problem selecting all of
the data columns (in which case, you probably have non-covering index
issues at the database level) then you will be far better off with
this approach. It's perfectly fine to generate predicates and sort
orders dynamically but you really don't want to do this with the
output list.
More about this
Yes You can do something like that with Dynamic query with Linq.
This is an example that you can build a custom query with Dynamic query with Linq:
string strWhere = string.Empty;
string strOrderBy = string.Empty;
if (!string.IsNullOrEmpty(txtAddress.Text))
strWhere = "Address.StartsWith(\"" + txtAddress.Text + "\")";
if (!string.IsNullOrEmpty(txtEmpId.Text))
{
if(!string.IsNullOrEmpty(strWhere ))
strWhere = " And ";
strWhere = "Id = " + txtEmpId.Text;
}
if (!string.IsNullOrEmpty(txtDesc.Text))
{
if (!string.IsNullOrEmpty(strWhere))
strWhere = " And ";
strWhere = "Desc.StartsWith(\"" + txtDesc.Text + "\")";
}
if (!string.IsNullOrEmpty(txtName.Text))
{
if (!string.IsNullOrEmpty(strWhere))
strWhere = " And ";
strWhere = "Name.StartsWith(\"" + txtName.Text + "\")";
}
EmployeeDataContext edb = new EmployeeDataContext();
var emp = edb.Employees.Where(strWhere);
grdEmployee.DataSource = emp.ToList();
grdEmployee.DataBind();
For more information you can check this page.