I have written a VB .Net front-end to the office SQL server. I want to expand this so users can access the server via a VPN. When testing the process, I can only see the SQL server via the VPN if I also have 'File and Printer Sharing' enabled on each client PC, so I want some code to check that both the VPN and the sharing are enabled before trying to access the server. I have the following three lines which checks that the VPN is running:
Dim myInterfaceList As NetworkInterface() = NetworkInterface.GetAllNetworkInterfaces
Dim myVPNRunning As Boolean = myInterfaceList.AsEnumerable().Any(Function(x) x.Name = "MyTestVPN")
If myVPNRunning Then chkProgress3.Checked = True
but I can't find a way (in VB .NET) to check if the client PC has 'File and Printer Sharing' enabled. I have found this article 1 which discusses using Powershell to check, but I don't know how to translate this into VB .Net code (or even if this is the correct / best way). The office network is not a Domain, so I can't enable sharing through Group Policy.
Has anyone else tackled this?
Thanks,
Tim
The following shows how to check if File and Printer sharing is enabled using PowerShell in VB.NET.
Download/install NuGet package for PowerShell
Microsoft.PowerShell.5.1.ReferenceAssemblies (.NET Framework)
Microsoft.PowerShell.SDK (.NET 6)
See this post for more information.
Add the following Imports statements:
Imports System.Management.Automation
IsFileAndPrinterSharingEnabled:
Private Function IsFileAndPrinterSharingEnabled() As Boolean
Using ps As PowerShell = PowerShell.Create()
Dim result As String = ps.AddScript("Get-NetAdapterBinding | ? {$_.DisplayName -eq 'File and Printer Sharing for Microsoft Networks' -and $_.Name -eq 'ethernet'} | Select -ExpandProperty Enabled").Invoke()(0).ToString()
'Dim result As String = ps.AddScript("Get-NetAdapterBinding | ? {$_.DisplayName -eq 'File and Printer Sharing for Microsoft Networks' -and $_.Name -eq 'ethernet'} | ForEach-Object {$_.Enabled}").Invoke()(0).ToString()
If result = "True" Then
Return True
End If
End Using
Return False
End Function
Resources:
File and Printer Sharing Status
Output data with no column headings using PowerShell
Other Resources
Can't access my SQL Server via C# form application from client device
SMB: File and printer sharing ports should be open
How to detect, enable and disable SMBv1, SMBv2, and SMBv3 in Windows
Related
I've built an app that contains a PowerShell runspace.
It works perfectly for everything I've thrown at it.... until today.
One bit of functionality I've added detects when the user is trying to access corporate resources and will highlight if their VPN has become disconnected.
We use an always on VPN with certificate auth, so no username/password is required.
The runspace, and the app it is contained within run in the context of the user.
I've been able to demonstrate this by outputting the current environment user from within the runspace, which comes back as me (i.e. not system or administrator or anything silly).
I have a really simple bit of PowerShell that will attempt 4 times to reconnect the VPN before reporting back a failure, checking before each retry for a success.
This code works perfectly if I run ISE (standard, NOT elevated), but when I pass this code to be executed I get an error coming back from rasdial, which simply says 'the data is invalid'.
The PowerShell is simple enough...
$myvpn = Get-VpnConnection | Where-Object {$_.ServerAddress -eq "vpn.example.com"}
$VPNStatus = ( $myvpn ).ConnectionStatus
if ($VPNStatus -eq "Connected")
{
$output= 0
}
else
{
$attempt = 0
DO
{
Write-output "not connected"
rasdial $myvpn.name
$check = (Get-VpnConnection | Where-Object {$_.ServerAddress -eq "vpn.example.com"}).ConnectionStatus
start-sleep -Seconds 4
$attempt ++
} Until ($attempt -gt 3 -or $check -eq "Connected")
if ($check -eq "Connected")
{
$output= 0
}
else
{
$output= 1
}
}
I've got no idea why it won't work in the runspace, but will work in normal PowerShell with ease. My first suspicion was the context, but like I said I've disproven that theory.
I've googled the hell out of the error and can't seem to find any related results.
I've also tried to work around it, spawning an actual PowerShell session (ie not in a runspace) and passing in the command, but that also fails when spawned from my app.
Has anyone seen this before or similar behaviour when using runspaces/pipelines etc. within vb.net?
Are there any weird permissions pitfalls that I may have overlooked?
Edit: Expanded the powershell to be fully inclusive
So I've got to the bottom of this just now... Turns out the PS command will only work in an x64 shell, and my app's runspace was being spawned x86.
I changed the app to target x64 and it worked first time.
This post on Technet was the breadcrumbs that got me over the finish line.
I have a Visual Studio form running with VB.net and I'm collecting info needed to setup an AD user. In the end, this info will need to simply be passed to Powershell with no return info needed. Before that though, I need it to check if a printer code has already been assigned to someone before allowing it to be submitted to another user. I have a simple powershell script written up for it.
(We use the Pager field to store the printer code.)
Import-Module ActiveDirectory
$Page = $args[0]
Get-ADUser -Filter { Pager -like $Page } | FT Name
I setup the code I found HERE, and attempted to modify it to my script but it keeps crashing on
Dim results As Collection(Of PSObject) = MyPipeline.Invoke()
It gives me: An unhandled exception of type 'System.Management.Automation.ParseException' occurred in System.Management.Automation.dll
If I run his little 6+5 basic example script, it works, but when I try to retrieve info and return a name, it doesn't like it. How can I get it to return the name of the person if it find it? And since it won't run, I'm not even sure if passing the printer code as $args[0] is going to work yet.
Your results is expecting a collection of PowerShell objects. When you pipe the Get-ADUser command to Format-Table, it effectively strips the object down to a stream of strings. Try without the | FT Name.
Import-Module ActiveDirectory #if you're using powershell 3 or later, this may be redundant
# $Page = $args[0] # don't need to do this
$results = Get-ADUser -Filter { Pager -like $args[0] }
Write-Verbose $results
#Write-Verbose $results.Name #try this if the above one works
Update:
Write-Verbose may be causing an issue.
Try this:
Get-ADUser -Filter { Pager -like $args[0] }
Just that one line as the total PS code. (Assuming you have PowerShell 3.0 or later, you don't need Import-Module) That line will return objects of type TypeName: Microsoft.ActiveDirectory.Management.ADUser (from `Get-ADUser username | Get-Member).
You may also be able to use the .Net object type directly, without PowerShell. I'm not knowledgeable about .NET beyond what I picked up working with PowerShell.
Accessing AD using .NET, info from MSDN.
The problem:
On a windows server 2012 r2 box, I'm trying to use Chef to programmatically replace a .dll command component (aka a vb 6 library that I've registered on the box using regsvr32.exe) but when I try to copy over the file, the app pool of the website has a lock on it. I'm not sure if it matters, but the w3wp process is set to run as 32 bit via IIS.
My Solution (which isn't working):
In order to fix it, I was thinking about using a command line tool to find the reference to the dll and then recycling the app pool that's using it. Unfortunately, while I can get SysInternals' process explorer to find the dll, Handles.exe (the supposed command line version of process explorer) does not return anything. I was hoping that someone might be able to tell me how I was using handles incorrectly, or if there was a better tool for this.
Process Explorer - it has found my dll ComHelper.dll
Handles via command line - it has not found my dll ComHelper.dll
-- Edit --
This is the output of handles when I point it at w3wp while running as Admin
I would suspect you are running into access issues. Are you running Handle from an elevated command prompt ? Are you able to get any output covering handles in w3wp.exe (by using the pid of the process in handle.exe command line) ?
Looking at the handle enum output of w3wp.exe it seems,
listdll.exe -d ComHelper.dll
may be what you are looking for. Handle seems to be focused on files opened not dlls loaded. listdll is a tool that can be downloaded from sysinternals.
Alright so 32 bitness did matter. I ended up having to resort to powershell as opposed to trying to use handles. The code for finding a PID that has a lock on your file is scattered around the internet, but here's the link:
http://blogs.technet.com/b/heyscriptingguy/archive/2013/12/01/weekend-scripter-determine-process-that-locks-a-file.aspx (it's marv the robot's answer at the bottom)
For the record, this is what was suggested
$lockedFile="C:\Windows\System32\acproxy.dll"
$isLocked = $false
Get-Process | foreach{
$processVar = $_;$_.Modules | foreach{
if($_.FileName -eq $lockedFile){
$isLocked = $true
$processVar.Name + " PID:" + $processVar.id
}
}
}
This is what I had translated it into with my powershell noobishness
$lockedFile = "E:\Components\___ComHelper.dll"
$list = Get-Process
foreach ($process in $list)
{
foreach ($module in $process.Modules)
{
if ($module.FileName -ne $lockedFile) { continue }
$process.Name + " PID:" + $process.Id
}
}
Shutting down a pc in vb.net is easy:
Process.Start("shutdown", "-s -t 00")
unless the user has locked the pc in which case the above fails.
How do I get around this in vb.net? How do I shutdown a locked PC?
The program will be running locally.
System.Diagnostics.Process.Start("shutdown", "-s -f -t 00")
This will force a shutdown In 00ms silently. The code you have to invoke each process is redundant, use the code above. Just do a System.Imports.IO at the top and you are good to go.
You could P/Invoke ExitWindowsEx
There is an example in C# there, but I'm sure you can convert it.
I think you're looking for the '-f' flag to force a shutdown.
Quote from a MS KB article:
When the computer is locked, you can shut down the computer, if you run the Shutdown.exe command together with the -f option.
For posterity:
Dim ms As ManagementScope = New ManagementScope("\\LocalHost")
ms.Options.EnablePrivileges = True
Dim oq As ObjectQuery = New ObjectQuery("SELECT * FROM Win32_OperatingSystem")
Dim query1 As ManagementObjectSearcher = New ManagementObjectSearcher(ms, oq)
Dim queryCollection1 As ManagementObjectCollection = query1.Get()
For Each mo As ManagementObject In queryCollection1
Dim ss As String() = {"5"}
mo.InvokeMethod("Win32Shutdown", ss)
Next
Google "Win32Shutdown" for more details of the flags available (ss above). 5 is a forced shutdown for when the pc is locked but it's more graceful than shutdown /f and doesn't appear to cause any problems with programs or services on restart.
Have a look at this article on CodeProject which illustrates forcing a computer to shutdown remotely to give you an idea on how to do it.
Using the System.Management namespace is more elegant than starting an external tool. Here is a code example in C#, which should be fairly easy to convert:
http://www.dreamincode.net/forums/topic/33948-how-to-shut-down-your-computer-in-c%23/
When using SQL Server Express 2005's User Instance feature with a connection string like this:
<add name="Default" connectionString="Data Source=.\SQLExpress;
AttachDbFilename=C:\My App\Data\MyApp.mdf;
Initial Catalog=MyApp;
User Instance=True;
MultipleActiveResultSets=true;
Trusted_Connection=Yes;" />
We find that we can't copy the database files MyApp.mdf and MyApp_Log.ldf (because they're locked) even after stopping the SqlExpress service, and have to resort to setting the SqlExpress service from automatic to manual startup mode, and then restarting the machine, before we can then copy the files.
It was my understanding that stopping the SqlExpress service should stop all the user instances as well, which should release the locks on those files. But this does not seem to be the case - could anyone shed some light on how to stop a user instance, such that it's database files are no longer locked?
Update
OK, I stopped being lazy and fired up Process Explorer. Lock was held by sqlserver.exe - but there are two instances of sql server:
sqlserver.exe PID: 4680 User Name: DefaultAppPool
sqlserver.exe PID: 4644 User Name: NETWORK SERVICE
The file is open by the sqlserver.exe instance with the PID: 4680
Stopping the "SQL Server (SQLEXPRESS)" service, killed off the process with PID: 4644, but left PID: 4680 alone.
Seeing as the owner of the remaining process was DefaultAppPool, next thing I tried was stopping IIS (this database is being used from an ASP.Net application). Unfortunately this didn't kill the process off either.
Manually killing off the remaining sql server process does remove the open file handle on the database files, allowing them to be copied/moved.
Unfortunately I wish to copy/restore those files in some pre/post install tasks of a WiX installer - as such I was hoping there might be a way to achieve this by stopping a windows service, rather then having to shell out to kill all instances of sqlserver.exe as that poses some problems:
Killing all the sqlserver.exe instances may have undesirable consequencies for users with other Sql Server instances on their machines.
I can't restart those instances easily.
Introduces additional complexities into the installer.
Does anyone have any further thoughts on how to shutdown instances of sql server associated with a specific user instance?
Use "SQL Server Express Utility" (SSEUtil.exe) or the command to detach the database used by SSEUtil.
SQL Server Express Utility,
SSEUtil is a tool that lets you easily interact with SQL Server,
http://www.microsoft.com/downloads/details.aspx?FamilyID=fa87e828-173f-472e-a85c-27ed01cf6b02&DisplayLang=en
Also, the default timeout to stop the service after the last connection is closed is one hour. On your development box, you may want to change this to five minutes (the minimum allowed).
In addition, you may have an open connection through Visual Studio's Server Explorer Data Connections, so be sure to disconnect from any database there.
H:\Tools\SQL Server Express Utility>sseutil -l
1. master
2. tempdb
3. model
4. msdb
5. C:\DEV_\APP\VISUAL STUDIO 2008\PROJECTS\MISSICO.LIBRARY.1\CLIENTS\CORE.DATA.C
LIENT\BIN\DEBUG\CORE.DATA.CLIENT.MDF
H:\Tools\SQL Server Express Utility>sseutil -d C:\DEV*
Failed to detach 'C:\DEV_\APP\VISUAL STUDIO 2008\PROJECTS\MISSICO.LIBRARY.1\CLIE
NTS\CORE.DATA.CLIENT\BIN\DEBUG\CORE.DATA.CLIENT.MDF'
H:\Tools\SQL Server Express Utility>sseutil -l
1. master
2. tempdb
3. model
4. msdb
H:\Tools\SQL Server Express Utility>
Using .NET Refector the following command is used to detach the database.
string.Format("USE master\nIF EXISTS (SELECT * FROM sysdatabases WHERE name = N'{0}')\nBEGIN\n\tALTER DATABASE [{1}] SET OFFLINE WITH ROLLBACK IMMEDIATE\n\tEXEC sp_detach_db [{1}]\nEND", dbName, str);
I have been using the following helper method to detach MDF files attached to SQL Server in unit tests (so that SQ Server releases locks on MDF and LDF files and the unit test can clean up after itself)...
private static void DetachDatabase(DbProviderFactory dbProviderFactory, string connectionString)
{
using (var connection = dbProviderFactory.CreateConnection())
{
if (connection is SqlConnection)
{
SqlConnection.ClearAllPools();
// convert the connection string (to connect to 'master' db), extract original database name
var sb = dbProviderFactory.CreateConnectionStringBuilder();
sb.ConnectionString = connectionString;
sb.Remove("AttachDBFilename");
var databaseName = sb["database"].ToString();
sb["database"] = "master";
connectionString = sb.ToString();
// detach the original database now
connection.ConnectionString = connectionString;
connection.Open();
using (var cmd = connection.CreateCommand())
{
cmd.CommandText = "sp_detach_db";
cmd.CommandType = CommandType.StoredProcedure;
var p = cmd.CreateParameter();
p.ParameterName = "#dbname";
p.DbType = DbType.String;
p.Value = databaseName;
cmd.Parameters.Add(p);
p = cmd.CreateParameter();
p.ParameterName = "#skipchecks";
p.DbType = DbType.String;
p.Value = "true";
cmd.Parameters.Add(p);
p = cmd.CreateParameter();
p.ParameterName = "#keepfulltextindexfile";
p.DbType = DbType.String;
p.Value = "false";
cmd.Parameters.Add(p);
cmd.ExecuteNonQuery();
}
}
}
}
Notes:
SqlConnection.ClearAllPools() was very helpful in eliminating "stealth" connections (when a connection is pooled, it will stay active even though you 'Close()' it; by explicitely clearing pool connections you don't have to worry about setting pooling flag to false in all connection strings).
The "magic ingredient" is call to the system stored procedure sp_detach_db (Transact-SQL).
My connection strings included "AttachDBFilename" but didn't include "User Instance=True", so this solution might not apply to your scenario
I can't comment yet because I don't have high enough rep yet. Can someone move this info to the other answer so we don't have a dupe?
I just used this post to solve my WIX uninstall problem. I used this line from AMissico's answer.
string.Format("USE master\nIF EXISTS (SELECT * FROM sysdatabases WHERE name = N'{0}')\nBEGIN\n\tALTER DATABASE [{1}] SET OFFLINE WITH ROLLBACK IMMEDIATE\n\tEXEC sp_detach_db [{1}]\nEND", dbName, str);
Worked pretty well when using WIX, only I had to add one thing to make it work for me.
I had took out the sp_detach_db and then brought the db back online. If you don't, WIX will leave the mdf files around after the uninstall. Once I brought the db back online WIX would properly delete the mdf files.
Here is my modified line.
string.Format( "USE master\nIF EXISTS (SELECT * FROM sysdatabases WHERE name = N'{0}')\nBEGIN\n\tALTER DATABASE [{0}] SET OFFLINE WITH ROLLBACK IMMEDIATE\n\tALTER DATABASE [{0}] SET ONLINE\nEND", dbName );
This may not be what you are looking for, but the free tool Unlocker has a command line interface that could be run from WIX. (I have used unlocker for a while and have found it stable and very good at what it does best, unlocking files.)
Unlocker can unlock and move/delete most any file.
The downside to this is the apps that need a lock on the file will no longer have it. (But sometimes still work just fine.) Note that this does not kill the process that has the lock. It just removes it's lock. (It may be that restarting the sql services that you are stopping will be enough for it to re-lock and/or work correctly.)
You can get Unlocker from here: http://www.emptyloop.com/unlocker/
To see the command line options run unlocker -H
Here they are for convenience:
Unlocker 1.8.8
Command line usage:
Unlocker.exe Object [Option]
Object:
Complete path including drive to a file or folder
Options:
/H or -H or /? or -?: Display command line usage
/S or -S: Unlock object without showing the GUI
/L or -L: Object is a text file containing the list of files to unlock
/LU or -LU: Similar to /L with a unicode list of files to unlock
/O or -O: Outputs Unlocker-Log.txt log file in Unlocker directory
/D or -D: Delete file
/R Object2 or -R Object2: Rename file, if /L or /LU is set object2 points to a text file containing the new name of files
/M Object2 or -M Object2: Move file, if /L or /LU is set object2 points a text file containing the new location of files
Assuming your goal was to replace C:\My App\Data\MyApp.mdf with a file from your installer, you would want something like unlocker C:\My App\Data\MyApp.mdf -S -D. This would delete the file so you could copy in a new one.