I'm using the following lines below to create a connection to a shared network location, but the problem is with any connections active (I think), network.MapNetworkDrive("..") will throw an error:
Multiple connections to a server or shared resource by the same user,
using more than one user name, are not allowed. Disconnect all previous
connections to the server or shared resource and try again.
I got pass this error by using net use * /delete from the command line, but is there an equivalent commands in C#?
IWshNetwork_Class network = new IWshNetwork_Class();
network.MapNetworkDrive("z:", #shared_path, Type.Missing, "Admin", "!QAZxsw2");
...
network.RemoveNetworkDrive("z:");
Just Use
System.Diagnostics.Process.Start("CMD.exe","/c net use * /delete /y");
Just use Process.Start
System.Diagnostics.Process.Start("CMD.exe","/c net use * /delete");
If you insist on a managed code approach (why?) you can do something like:
foreach(var letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
{
try {
network.RemoveNetworkDrive(letter + ":");
} catch {}
}
or better yet (depending on how flexible you are with the required behviour), iterate on this instead:
// assumes using System.IO
var networkDrives = DriveInfo.GetDrives().Where(x => x.DriveType == DriveType.Network))
foreach(var networkDrive in networkDrives)
I'd still opt for just using Process.Start as being far cleaner and more reliable. Once you start down the road of re-implementing 'trivial' functionality you often quickly find out how non-trivial it really is.
Further on why your question itself might need reconsidering - why would you insist on removing all network drive mappings anyway? If you are mapping Z: for example, you know what drive needs to be unmapped. You can have something like this:
public void MapDrive(char driveLetter, string networkPath, string userName, string password)
{
try { network.RemoveNetworkDrive(driveLetter + ":"); } catch {}
network.MapNetworkDrive(driveLetter + ":", #shared_path, Type.Missing, username, password);
}
because surely if you're creating the drive mappings you inherently know which drive letters need to be free as opposed to blowing all network drive mappings away.
Related
Is there a way to dump the generated sql to the Debug log or something? I'm using it in a winforms solution so the mini-profiler idea won't work for me.
I got the same issue and implemented some code after doing some search but having no ready-to-use stuff. There is a package on nuget MiniProfiler.Integrations I would like to share.
Update V2: it supports to work with other database servers, for MySQL it requires to have MiniProfiler.Integrations.MySql
Below are steps to work with SQL Server:
1.Instantiate the connection
var factory = new SqlServerDbConnectionFactory(_connectionString);
using (var connection = ProfiledDbConnectionFactory.New(factory, CustomDbProfiler.Current))
{
// your code
}
2.After all works done, write all commands to a file if you want
File.WriteAllText("SqlScripts.txt", CustomDbProfiler.Current.ProfilerContext.BuildCommands());
Dapper does not currently have an instrumentation point here. This is perhaps due, as you note, to the fact that we (as the authors) use mini-profiler to handle this. However, if it helps, the core parts of mini-profiler are actually designed to be architecture neutral, and I know of other people using it with winforms, wpf, wcf, etc - which would give you access to the profiling / tracing connection wrapper.
In theory, it would be perfectly possible to add some blanket capture-point, but I'm concerned about two things:
(primarily) security: since dapper doesn't have a concept of a context, it would be really really easy for malign code to attach quietly to sniff all sql traffic that goes via dapper; I really don't like the sound of that (this isn't an issue with the "decorator" approach, as the caller owns the connection, hence the logging context)
(secondary) performance: but... in truth, it is hard to say that a simple delegate-check (which would presumably be null in most cases) would have much impact
Of course, the other thing you could do is: steal the connection wrapper code from mini-profiler, and replace the profiler-context stuff with just: Debug.WriteLine etc.
You should consider using SQL profiler located in the menu of SQL Management Studio → Extras → SQL Server Profiler (no Dapper extensions needed - may work with other RDBMS when they got a SQL profiler tool too).
Then, start a new session.
You'll get something like this for example (you see all parameters and the complete SQL string):
exec sp_executesql N'SELECT * FROM Updates WHERE CAST(Product_ID as VARCHAR(50)) = #appId AND (Blocked IS NULL OR Blocked = 0)
AND (Beta IS NULL OR Beta = 0 OR #includeBeta = 1) AND (LangCode IS NULL OR LangCode IN (SELECT * FROM STRING_SPLIT(#langCode, '','')))',N'#appId nvarchar(4000),#includeBeta bit,#langCode nvarchar(4000)',#appId=N'fea5b0a7-1da6-4394-b8c8-05e7cb979161',#includeBeta=0,#langCode=N'de'
Try Dapper.Logging.
You can get it from NuGet. The way it works is you pass your code that creates your actual database connection into a factory that creates wrapped connections. Whenever a wrapped connection is opened or closed or you run a query against it, it will be logged. You can configure the logging message templates and other settings like whether SQL parameters are saved. Elapsed time is also saved.
In my opinion, the only downside is that the documentation is sparse, but I think that's just because it's a new project (as of this writing). I had to dig through the repo for a bit to understand it and to get it configured to my liking, but now it's working great.
From the documentation:
The tool consists of simple decorators for the DbConnection and
DbCommand which track the execution time and write messages to the
ILogger<T>. The ILogger<T> can be handled by any logging framework
(e.g. Serilog). The result is similar to the default EF Core logging
behavior.
The lib declares a helper method for registering the
IDbConnectionFactory in the IoC container. The connection factory is
SQL Provider agnostic. That's why you have to specify the real factory
method:
services.AddDbConnectionFactory(prv => new SqlConnection(conStr));
After registration, the IDbConnectionFactory can be injected into
classes that need a SQL connection.
private readonly IDbConnectionFactory _connectionFactory;
public GetProductsHandler(IDbConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
The IDbConnectionFactory.CreateConnection will return a decorated
version that logs the activity.
using (DbConnection db = _connectionFactory.CreateConnection())
{
//...
}
This is not exhaustive and is essentially a bit of hack, but if you have your SQL and you want to initialize your parameters, it's useful for basic debugging. Set up this extension method, then call it anywhere as desired.
public static class DapperExtensions
{
public static string ArgsAsSql(this DynamicParameters args)
{
if (args is null) throw new ArgumentNullException(nameof(args));
var sb = new StringBuilder();
foreach (var name in args.ParameterNames)
{
var pValue = args.Get<dynamic>(name);
var type = pValue.GetType();
if (type == typeof(DateTime))
sb.AppendFormat("DECLARE #{0} DATETIME ='{1}'\n", name, pValue.ToString("yyyy-MM-dd HH:mm:ss.fff"));
else if (type == typeof(bool))
sb.AppendFormat("DECLARE #{0} BIT = {1}\n", name, (bool)pValue ? 1 : 0);
else if (type == typeof(int))
sb.AppendFormat("DECLARE #{0} INT = {1}\n", name, pValue);
else if (type == typeof(List<int>))
sb.AppendFormat("-- REPLACE #{0} IN SQL: ({1})\n", name, string.Join(",", (List<int>)pValue));
else
sb.AppendFormat("DECLARE #{0} NVARCHAR(MAX) = '{1}'\n", name, pValue.ToString());
}
return sb.ToString();
}
}
You can then just use this in the immediate or watch windows to grab the SQL.
Just to add an update here since I see this question still get's quite a few hits - these days I use either Glimpse (seems it's dead now) or Stackify Prefix which both have sql command trace capabilities.
It's not exactly what I was looking for when I asked the original question but solve the same problem.
I'm using MaikKit, and am trying to work out how to get the mail in a Jim folder I set up in a Gmail account. I tried enumerating the folders as follows...
private void GetFolders() {
using ImapClient client = new();
EmailAccount emailAccount = EmailAccountOptions.Value;
client.Connect(emailAccount.Server, emailAccount.Port, SecureSocketOptions.SslOnConnect);
client.Authenticate(emailAccount.UserName, emailAccount.Password);
foreach (FolderNamespace ns in client.PersonalNamespaces) {
IMailFolder folder = client.GetFolder(ns);
_msg += $"<br/>Ns: {ns.Path} / {folder.FullName}";
foreach (IMailFolder subfolder in folder.GetSubfolders()) {
_msg += $"<br/> {subfolder.FullName}";
}
}
try {
IMailFolder jim = client.GetFolder(new FolderNamespace('/', "Jim"));
jim.Open(FolderAccess.ReadOnly);
_msg += $"<br/>Got Jim, has {jim.Count} email(s)";
}
catch (Exception ex) {
_msg += $"<br/>Ex ({ex.GetType()}): {ex.Message}";
}
}
This shows the following...
Ns: /
INBOX
Jim
[Gmail]
✔
✔✔
Got Jim, has 1 email(s)
So, it seems I can access Jim without problem. As the purpose of this was to access Jim only, the enumeration isn't needed. However, when I removed it, I got an exception...
Ns: /
Ex (MailKit.FolderNotFoundException): The requested folder could not be found
After some trial and error, I found out that the following works...
private void GetFolders() {
using ImapClient client = new();
EmailAccount emailAccount = EmailAccountOptions.Value;
client.Connect(emailAccount.Server, emailAccount.Port, SecureSocketOptions.SslOnConnect);
client.Authenticate(emailAccount.UserName, emailAccount.Password);
foreach (FolderNamespace ns in client.PersonalNamespaces) {
IMailFolder folder = client.GetFolder(ns);
var subs = folder.GetSubfolders();
}
try {
IMailFolder jim = client.GetFolder(new FolderNamespace('/', "Jim"));
jim.Open(FolderAccess.ReadOnly);
_msg += $"<br/>Got Jim, has {jim.Count} email(s)";
}
catch (Exception ex) {
_msg += $"<br/>Ex ({ex.GetType()}): {ex.Message}";
}
}
...but if I remove the call to folder.GetSubfolders() it throws an exception.
If I change the code to look for INBOX instead...
IMailFolder jim = client.GetFolder(new FolderNamespace('/', "INBOX"));
...then it works fine even without the call to folder.GetSubfolders().
Anyone any idea why this happens? As far as I can see, the foreach loop isn't doing anything that should affect Jim.
Just so it's clear, using ImapClient.GetFolder (FolderNamespace) the way you are using it is not the correct way of using that API.
A few general API rules to consider when using MailKit:
All APIs that require network I/O take a CancellationToken argument. So if a *Client or *Folder API doesn't take a CancellationToken, that means it's a cache lookup or something else that doesn't require hitting the network.
MailKit APIs don't require you to call a constructor just to pass a string to the method ;-)
With the ImapClient.GetFolder(FolderNamespace) API, there is an equivalent ImapFolder.GetFolder(string path, CancellationToken) API that is meant to be used for such tasks.
(Note: The only reason that the FolderNamespace .ctor is public is because someone out there may want to implement the MailKit IMailStore or IImapClient interfaces and will need to be able to create FolderNamespace instances).
That said, I am loathe to recommend this API because MailKit's ImapFolder was designed to get folders 1 level at a time via the ImapFolder.GetSubfolders() and/or ImapFolder.GetSubfolder() APIs and so the ImapClient.GetFolder() API is a huge hack that has to recursively fetch folder paths and string them together.
Keep in mind that ImapFolder instances have a ParentFolder property that the MailKit API guarantees will always be set.
That makes things difficult to guarantee if the developer can just request rando folders anywhere in the tree at the ImapClient level using a full path.
Anyway... yea, the Imapclient.GetFolder (string path, CancellationToken) API exists and it works and you can use it if you want to, but let it be known that I hate that API.
Okay, so now on to why your client.GetFolder(new FolderNamespace(...)) hack doesn't work if you remove the call to GetSubfolders()
As you've discovered, IMailFolder jim = client.GetFolder(new FolderNamespace('/', "Jim")); only works if a call such as var subs = folder.GetSubfolders(); exists before it that happens to return the /Jim folder as one of the subfolders.
Your question is: why?
(or maybe your question is: why doesn't it work without that line?)
Either way, the answer is the same.
MailKit's ImapClient keeps a cache of known folders that exist in the IMAP folder tree so that it can quickly and easily find the parent of each folder and doesn't have to explicitly call LIST on each parent node in the path in order to build the ImapFolder chain (remmeber: each ImapFolder has a ParentFolder property that points to an ImapFolder instance that represents its direct parent all the way back up to the root).
So... that GetSubfolders() call is adding the /Jim folder to the cache.
Since the GetFolder (new FolderNamesdpace ('/', "Jim")) call cannot go out to the network, it relies on the internal cache. The only folders that exist in the cache after connecting are the folders that are returned in the NAMESPACE command and INBOX. Everything else gets added to the cache as they become known due to LIST responses (as a result of GetSubfolders() and GetSubfolder() calls).
I am trying to write a WLAN fingerprinting program using NativeWifi in C#. To do this i run a loop to get the wlan information many times and then later use matlab to average / analyze the data.
The problem is that i get all the same values, even as i move about the house, when the program is running. From the internet i've seen that there is a cache that stores the data of available networks. I was wondering if there is a system call which resets this cache.
I have also seen this using the cmd call
netsh wlan show networks mode=bssid
this gives me the same values until i open the available wifi networks in my OS and if i run it again after, it will give different values.
edit: This system will be for my use only, so i would be comfortable starting over on a linux platform if there is a known library that can handle this for me. I don't even know what to google to even get the information, though. Anything related to "network cache" takes me to help threads of unrelated topics...
I will provide the relevant part of my code below:
public void get_info_instance(StreamWriter file)
{
try
{
foreach (WlanClient.WlanInterface wlanIface in client.Interfaces)
{
Wlan.WlanBssEntry[] wlanBssEntries = wlanIface.GetNetworkBssList();
foreach (Wlan.WlanBssEntry network in wlanBssEntries)
{
int rss = network.rssi;
byte[] macAddr = network.dot11Bssid;
string tMac = "";
for (int i = 0; i < macAddr.Length; i++)
{
tMac += macAddr[i].ToString("x2").PadLeft(2, '0').ToUpper();
}
file.WriteLine("Found network: " + System.Text.ASCIIEncoding.ASCII.GetString(network.dot11Ssid.SSID).ToString());
file.WriteLine("Signal: " + network.linkQuality + "%");
file.WriteLine("BSS Type: " + network.dot11BssType + ".");
file.WriteLine("RSSID: " + rss.ToString());
file.WriteLine("BSSID: " + tMac);
file.WriteLine(" ");
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Internally, netsh is powered by this API. What this means, is that calling netsh wlan show networks mode=bssid just returns the cache of the networks that showed up during the last scan. This is what you've discovered.
This means that in order to refresh this cache, you need to trigger a scan. If your C# library you are using includes it, you could make this happen on demand with a call to WlanScan. I am not sure which C# wrapper you are using, but it probably includes this function. When you get a scan complete notification (register with source WLAN_NOTIFICATION_SOURCE_ACM and look out for wlan_notification_acm_scan_list_refresh), the cache should be updated.
If you let me know which C# library you are using, maybe I can point you to the relevant functions.
You mentioned that opening the available networks causes the cache to refresh. This is because opening the available networks triggers a call to WlanScan.
Profiles are not relevant to the available network list -- profiles are what the Wlan service uses to keep track of which networks are configured on your machine -- deleting them does not make WlanSvc scan again. It may be a coincidence that deleting them happens to coincide with a scan, but it is more of a side effect than the designed usage.
edit: to subscribe to notifications using the Managed Wifi API you are using, this snippet should work:
wlanIface.WlanNotification += wlanIface_WlanNotification;
And the callback:
static void wlanIface_WlanNotification(Wlan.WlanNotificationData notifyData)
{
if (notifyData.notificationCode == (int)Wlan.WlanNotificationCodeAcm.ScanComplete)
{
Console.WriteLine("Scan Complete!");
}
}
You can test this by running this, then opening the available networks on Windows. You should see "Scan Complete" shortly after you open it each time. You can use a messagebox instead of Console.WriteLine if you prefer.
To trigger a scan yourself:
wlanIface.Scan();
for all
netsh wlan delete profile name=* i=*
you might not want to do it on all interfaces, and hard code the interface in there for faster result
Is there a safe way to remove the domain name from a machine name in C#?
Example:
MYMACHINE.mydomain.com
should be resolved to
MYMACHINE
Naive String Approach:
string fullName = "MYMACHINE.mydomain.com";
string resolvedMachineName = fullName.Split('.')[0];
If you want the 'basic' understanding of machine name, then use :
Environment.MachineName
It provides the machine name without the domain (traditionally the COMPUTERNAME variable at a command prompt). See: http://msdn.microsoft.com/en-us/library/system.environment.machinename.aspx
Edit #1
#Antineutrino - as I re-read my answer I realized we don't have enough information to give you a correct answer. When you say 'machine name', however, it is not clear if you want to know how to parse the string you have, or the proper way to retrieve what you want. If you simply want to know how to parse a string, the other answers are correct. On the other hand...
Do you want the NetBios name or Cluster Name? Then Environment.MachineName
Do you want the underlying name of a the machine running a cluster? You'll need to dig into the WMI classes.
Do you want the most specific name from all DNS strings that resolve to the current machine? i.e. web1 from web1.mysite.com? You'll need to resolve all the IP's bound to the machine.
If you go back and edit/revise your question with more details -- you'll get better/more-specific answers.
Edit #2
If I understand correctly, your scenario is as follows: You have similar code that runs on both client and server machines in a desktop environment. In all machines registries you've saved a DNS name for the server (server.myapp.com). Now in the code you want to determine if a machine is the server or not.
What You Asked For
string serverName = Registry.GetValue(REG_KEY, REG_VALUE, "key missing");
string currentMachine = Environment.MachineName;
if (currentMachine.ToLower() == serverName.ToLower().Split('.')[0] ) {
//do something fancy
};
Another Idea
Instead of shrinking the server name, you can grow the machine name.
string serverName = Registry.GetValue(REG_KEY, REG_VALUE, "key missing");
string currentMachine = Environment.MachineName +
Environment.GetEnvironmentVariable("USERDNSDOMAIN");
if (currentMachine.ToLower() == serverName.ToLower() ) {
//do something fancy
};
What you may want
Distributing registry keys seems a bit cumbersome. If it were me I would probably do one/some/many of the following:
Create different build configurations and use pre-processor directives to change the code. Something very roughly like: #if(RELEASE_SERVER) { /*do server code*/} #if(RELEASE_CLIENT) { /*do server code*/}
Use app.config ---that's what it's there to store configuration data. No need to pollute the registry. app.config can be transformed on build/deployment, or simply modified manually on the one server.
Store the majority of the configuration data in a central location. This way the app only need to know were to retrieve it's configuration from (even if that's itself).
I'm looping through a network directory and trying to output the user/group names (permissions) associated with each file/folder. I'm getting the SID's back but I want the names like "group_test" and not "S-1-5-32-544". Here's my code -
var files = Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly);
foreach (var f in files2)
{
var fileInfo = new FileInfo(f);
var fs = fileInfo.GetAccessControl(AccessControlSections.Access);
foreach (FileSystemAccessRule rule in fs.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)))
{
var value = rule.IdentityReference.Value;
Response.Write(string.Format("File: {0} \t Usergroup: {1} <br/>", fileInfo.Name, value));
} }
I get SID's from the above code but in the foreach loop, if I use this instead -
(NTAccount)((SecurityIdentifier)rule.IdentityReference).Translate(typeof(NTAccount)).Value
I get this exception -
Some or all identity references could not be translated.
It appears that the Translate method does not work on remote shares. How do I retrieve the real names of the SID's? The remote server does not have LDAP.
Thank you.
The problem is that you are trying to resolve a SID that is local to a remote machine. As the answer to this question states:
The SecurityReference object's Translate method does work on non-local SIDs but only for domain accounts...
This link provides an example for remotely resolving a SID using WMI which is probably the best method for accomplishing your task.
If you can use WMI you should be able to do it via the Win32_UserAccount class I think. It has a Name property and a SID property.
Or the Win32_Group class for the groups.
Here's an article for connecting to a remote pc using WMI that has C# code: How To: Connect to a Remote Computer