I have the following code:
Public Shared Function MyTest() As SqlString
Dim rc As String = Nothing
Dim impersonatedUser As WindowsImpersonationContext = Nothing
If SqlContext.IsAvailable Then
If SqlContext.WindowsIdentity IsNot Nothing Then
impersonatedUser = SqlContext.WindowsIdentity.Impersonate
End If
End If
Try
rc = System.IO.File.Exists("C:\Data Files\Test\42.txt").ToString
Catch ex As Exception
Return ex.Message
Finally
If impersonatedUser IsNot Nothing Then
impersonatedUser.Undo()
End If
End Try
Return rc
End Function
In SQL the declation of the assembly is as follows:
CREATE ASYMMETRIC KEY aKeyCLR FROM EXECUTABLE FILE = '$(BASE)CLR.dll'
CREATE LOGIN CLRLogin FROM ASYMMETRIC KEY aKeyCLR
GRANT UNSAFE ASSEMBLY TO CLRLogin
The create function:
CREATE FUNCTION dbo.Test()
RETURNS NVARCHAR(4000)
AS
EXTERNAL NAME myStuff.[CLR.FileFunctions].[MyTest]
GO
When I execute SELECT Test() the file access is still done by account "NT SERVICE\MSSQLSERVER". I am logged on to SQL server with Windows Authentication and would expect that user to do the file access.
What am I missing here?
Your code appears to be correct. You can test by allowing read access only to your Windows Login and then changing the Exists() into string _Test = ReadAllText() or something like that.
What you are initially seeing is the main identity of the process. Impersonating another account is not a replacement of the original security context, it is simply a new SID (Security ID) to run the process as. In fact, impersonating an OS account does not change the "active" User in the Registry (i.e. HKEY_CURRENT_USER), nor does it reset the environment variables (i.e. PATH, TEMP, etc); those actions happen upon a full login.
Within SQL Server, when impersonating via EXECUTE AS, you can see the main identity of the Session via the ORIGINAL_LOGIN() built-in function.
By clicking one step further into ProcMon on the CreateFile event, the event properties reveal the impersonating user.
Working as designed!
Related
In VB.NET, I want to check whether the table "LEDGER_FULL" exists or not.
I'm using this query:
SELECT MSysObjects.Name, MSysObjects.Type
FROM MSysObjects
WHERE (MSysObjects.Name='LEDGER_FULL') AND (MSysObjects.Type=1)
My database is an MS-Access database. Once it execute the above query, I get this exception:
Record(s) cannot be read; no read permission on ‘MSYSObjects’
How to solve this?
Don't use MS Access system objects. In most cases, you'll have permission issues. You can access the database information (e.g., tables, views, etc.) using OleDbConnection.GetSchema.
Here's the function I use to check if a table exists or not:
Public Shared Function TableExists(tableName As String) As Boolean
Using conn As New OleDbConnection(ConnectionString)
conn.Open()
Dim dt As DataTable = conn.GetSchema("Tables")
Dim tables As String() = dt.AsEnumerable().Select(Function(dr) dr.Field(Of String)("TABLE_NAME")).ToArray
Return tables.Contains(tableName)
End Using
End Function
Usage:
If Not TableExists("LEDGER_FULL") Then
' The table doesn't exist. Do something about it.
End If
Hope that helps.
Over the last few days, I was asked to move a company program over from an Access back-end, to SQL Server.
There are 2 copies of the program, the live data version, on the server, and the local version on my PCs C: Drive, to ensure if I make a mistake, it doesn't affect the live data.
So, I managed to migrate the Access database, tables and data over to SQL Server 2008, and the local version of the program now works.
The easiest way, or so I'm informed, to now do the same to the live version of the program, is to write an imports program, which wipes all of the data from each table in the SQL Server database, and then copies over the data from the live Access database. However, I've never done this before, so I'm not really even sure where to begin.
Could anybody point me in the right direction on how to begin or do this, so that I only have to change the connection path in the program, rather than go through the whole process again?
PS, I work in vb.net, so that's the language I would need any responses in!
Thanks.
Usually one uses the SQL Server Import and Export Wizard for this.
It's a separate tool that is installed with SQL Server Management Studio (SSMS).
ANSWER
Step 1;
I added a new path to the ini file for the database to read. This connected to the live database. Once this connection is open in the project, proceed to step 2.
Step 2;
Create a new class, where the imports and exports will happen.
Step 3;
Put a button, or some sort of control in the program to initiate the import/export. For example, I had a button which, when clicked, asked the user to confirm that they wanted to import a new database and overwrite the existing one. If yes, call the function which does this, in the newly made imports class.
Step 4;
Now that you know how to get this set up, the code would be something like
Public Function importdatabase(/connections go in here/)
Declare transaction
Create sql variable
Try
Begin the transaction here
sql to delete the data from one table
sql to select all data from database that is being imported
For loop to iterate over each record in the database table
Declare a variable for each field in the database
variable1 = ("fieldname1")
variable2 = ("fieldname2")
sql statement to insert the new values
call to the function which runs the sql query
Next
commit transaction
Catch ex As Exception
Throw
End Try
Step 5; Repeat the delete/insert process for each database table
Below this, I have other functions.
One function created a new datatable, this is referenced as
For each dr as datarow in /functionname(parameters).Rows
Next one is to execute the sql statement (not required, any command to execute it will do)
Next one is used for parameterising my SQL query
The rest are to replace null values in the database with empty strings, set dates, etc
You can use the following class to import table(s) in access to sql server.
You need:
- The connection string of the source (including access file name) and the target one.
- The Source Table ,target tatble (if null it is the same as the source table)
Class ImportHelper
'modify connectionstring as needed
Public Property SourceConnectionString() As String
Get
Return m_SourceConnectionString
End Get
Set
m_SourceConnectionString = Value
End Set
End Property
Private m_SourceConnectionString As String
Public Property DestinationConnectionString() As String
Get
Return m_DestinationConnectionString
End Get
Set
m_DestinationConnectionString = Value
End Set
End Property
Private m_DestinationConnectionString As String
Public Sub New(sourceConnectionString__1 As String, destinationConnectionString__2 As String)
SourceConnectionString = sourceConnectionString__1
DestinationConnectionString = destinationConnectionString__2
End Sub
Public Sub Import(sourceTable As String, Optional targetTable As String = Nothing)
Using sourceConnection = New OleDbConnection(SourceConnectionString)
If String.IsNullOrEmpty(targetTable) Then
targetTable = sourceTable
End If
sourceConnection.Open()
' Perform an initial count on the destination table.
Dim commandRowCount = New OleDbCommand(Convert.ToString("SELECT COUNT(*) FROM ") & sourceTable, sourceConnection)
Dim countStart As Long = Convert.ToInt32(commandRowCount.ExecuteScalar())
Console.WriteLine("Source Table [{0}] has {1} rows", sourceTable, countStart)
' Get data from the source table
Dim commandSourceData = New OleDbCommand(Convert.ToString("SELECT * FROM ") & sourceTable, sourceConnection)
Dim reader = commandSourceData.ExecuteReader()
'---------------
Using destinationConnection As New SqlConnection(DestinationConnectionString)
destinationConnection.Open()
Using bulkCopy As New SqlBulkCopy(destinationConnection)
bulkCopy.DestinationTableName = targetTable
Try
' Write from the source to the destination.
bulkCopy.WriteToServer(reader)
Console.WriteLine(Convert.ToString("Sucess Importing ") & sourceTable)
Catch ex As Exception
Console.WriteLine(ex.Message)
Finally
reader.Close()
End Try
'using
End Using
'using
End Using
End Using
'using
End Sub
End Class
How to use:
Private Sub Test()
'modify connectionstring as needed
'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\mydatabase.mdb;User Id=admin;Password=; //access 97..2000
Dim SourceConnectionString As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\temp\database1.accdb;Persist Security Info=False;"
Dim DestinationConnectionString As String = "Data Source=xxxx;Initial Catalog=test;user=xxx;password=xxx;"
New ImportHelper(SourceConnectionString, DestinationConnectionString).Import("table1", "test1")
End Sub
I have created a table in my Access front end application, and try to connect it to the back end database with the following subroutine:
Sub createFlowTable()
Dim db As Database
Dim tblDef As TableDef
Set db = CurrentDb
Set tblDef = db.CreateTableDef("myTable")
tblDef.Connect = "ODBC;DRIVER=SQL Server;SERVER=myServer; Trusted_Connection=No;UID=<myUID>;PWD=<myPWD>;APP=2007 Microsoft Office system;DATABASE=myDataBase;Network=DBMSSOCN;TABLE=dbo.myTable"
tblDef.SourceTableName = "mySourceTableName"
db.TableDefs.Append tblDef
End Sub
After I close the front end Access database, and upon reopening it, the table fails to open. Even though I have set the Trusted_Connection to "No" in my string, the table still tries to use the Windows Authentication. Also, when I open the table on design view, I see in front of "Description":
ODBC;DRIVER=SQL Server;Server=myServer;APP=2007 Microsoft Office System;DATABASE=myDatabase;Network=DBMSSOCN;Table=dbo.myTable
So obviously Access has not saved the UID and PWD, nor has it saved the instruction on setting the Trusted_Connection to "No".
I insist to get this done with the connection string, and using DSN will not work for the purpose of my application. Help would be greatly appreciated.
You need to add the dbAttachSavePWD-Attribute to the created table to store your credentials with the linked table in Access.
Before appending the tabledef you should put this line of code:
tblDef.Attributes = (tblDef.Attributes Or dbAttachSavePWD)
We attached the database PStorage to the server.
Then I tried to create the login & user using the following code:
Dim con As New SqlConnection
Dim query As SqlCommand
con.ConnectionString = "Server=(LocalHost);Data Source=LocalHost\SQLEXPRESS;Database=PSTORAGE;Integrated Security=TRUE"
con.Open()
query.CommandText = "IF NOT EXISTS(SELECT [LoginName] FROM MASTER.DBO.SYSLOGINS WHERE [Name]='UserCP') CREATE LOGIN UserCP WITH PASSWORD='CPPassword'"
query.ExecuteNonQuery()
query.Dispose()
query.CommandText = "IF NOT EXISTS(SELECT [Name] FROM SYS.DATABASE_PRINCIPALS WHERE [Name]='CPUser') CREATE USER CPUser FOR LOGIN UserCP"
query.ExecuteNonQuery() 'This line is throwing the error -> Login Failed for the User 'UserCP'.
query.Dispose()
The error we are getting after executing the second query is
Login failed for the user 'UserCP'
While attaching the database the same error occurs. Then we had to use sqlCmd.
In all the systems this method works fine. But in one of our customers system this problem occurs. What might be the reason?
After a lot of tries we could come to know that the folder was compressed [All the file names were in blue colour]. Since the database files were inside the compressed folder- none of the operations could be done on them perfectly.
Right click on the database folder, Press on Properties
Press on Advanced
Untick the Compress contents to save disk space
Save the changes made
I got a sql procedure from a CLR (.net Assembly) that when executed returns an error
Msg 6522, Level 16, State 1, Procedure sp_HelloWorld, Line 0
A .NET Framework error occurred during execution of user defined routine or aggregate 'sp_HelloWorld':
System.Security.SecurityException: Request for the permission of type 'System.Data.SqlClient.SqlClientPermission, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.
System.Security.SecurityException:
at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet)
at System.Security.PermissionSet.Demand()
at System.Data.Common.DbConnectionOptions.DemandPermission()
at System.Data.SqlClient.SqlConnection.PermissionDemand()
at System.Data.SqlClient.SqlConnectionFactory.PermissionDemand(DbConnection outerConnection)
at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
at System.Data.SqlClient.SqlConnection.Open()
at HelloWorld.SQLCLR.HelloWorld()
This is my SQL script
go
drop procedure HelloWorld
drop assembly HelloWorld
GO
create assembly HelloWorld from 'F:\HelloWorld.dll'
with permission_set = safe
Go
create procedure sp_HelloWorld
as external name HelloWorld.[HelloWorld.SQLCLR].HelloWorld
go
exec sp_HelloWorld
and this is my Class (Assembly)
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using System.Security.Permissions;
using System.Data;
namespace HelloWorld
{
public class SQLCLR
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void HelloWorld()
{
string connectString1 = #"Data Source=localhost;Initial Catalog=ItemData;Integrated Security=True";
SqlClientPermission permission = new SqlClientPermission(PermissionState.None);
permission.Add(connectString1, "", KeyRestrictionBehavior.AllowOnly);
permission.PermitOnly();
SqlConnection sqlcon = new SqlConnection(connectString1);
sqlcon.Open();
SqlCommand sqlcmd = new SqlCommand("SELECT Top 1 * FROM ItemData.dbo.Item", sqlcon);
SqlDataReader reader = sqlcmd.ExecuteReader();
SqlContext.Pipe.Send(reader);
sqlcon.Close();
}
}
}
The problem is simply that you are attempting to access an external resource in an Assembly that is marked as SAFE. Accessing external resources requires setting the Assembly to at least EXTERNAL_ACCESS (and in some cases UNSAFE). However, looking at your code, you are simply trying to connect to the local instance, and in that case there is a far easier (and faster) means of doing this: using "Context Connection = true;" as the ConnectionString.
The Context Connection is a direct connection to the current process / session, and it is sometimes referred to as the in-process connection. The benefits of using the Context Connection are:
can be done in Assemblies marked as SAFE
access to local temporary objects (temp tables and temp procedures, both with names starting with a single # instead of double ##)
access to SET CONTEXT_INFO and CONTEXT_INFO()
no connection startup overhead
Also:
whether you use the in-process, Context Connection or a regular / external connection, you do not need to formally request permission using SqlClientPermission
you should always clean up external resources by calling their Dispose() method. Not all objects have this, but SqlConnection, SqlCommand, and SqlDataReader certainly do. It is typical for people to wrap disposable objects in a using() block as it is a compiler macro that expands to a try / finally structure that calls the Dispose() method in the finally to ensure that it is called, even if an error occurs.
The Dispose() method of many / most disposable objects automatically handles the call to Close() so you usually do not need to call Close() explicitly.
Your code should look as follows:
[Microsoft.SqlServer.Server.SqlProcedure]
public static void HelloWorld()
{
using (SqlConnection sqlcon = new SqlConnection("Context Connection = true;")
{
using (SqlCommand sqlcmd = new SqlCommand("SELECT Top 1 * FROM ItemData.dbo.Item",
sqlcon))
{
sqlcon.Open();
using (SqlDataReader reader = sqlcmd.ExecuteReader())
{
SqlContext.Pipe.Send(reader);
}
}
}
}
I just wanted to add my two sense to this. I'm doing something very similiar and I'm getting the same error. Here is what I found, however b/c I don't have this level of access to the DB I can't test it.
Easiest( although not MSDN recommended just to jet a CLR proc to run)
is to set the permission level to External_Access...
SQL Server Host Policy Level Permission Sets The set of code access
security permissions granted to assemblies by the SQL Server host
policy level is determined by the permission set specified when
creating the assembly. There are three permission sets: SAFE,
EXTERNAL_ACCESS and UNSAFE.
The permision level is set on the properties pages of the CLR project
, database tab - set Permission Level-External, set Aassembly
Owner-dbo, and run tsql 'ALTER DATABASE DataBaseName SET TRUSTWORTHY
ON' This will get the job DONE! - and the SmtpClient wiill work ok...
Then do it right and Sign the Assenbly with a Strong name Key file...
Full Post Here...
Have you set your DB set to Trusrtworth ON and enabled clr?
Try this
sp_configure 'clr enabled', 1
GO
RECONFIGURE
GO
ALTER DATABASE [YourDatabase] SET TRUSTWORTHY ON
GO
I have a guide here on how to use CLR Stored Procedures that might help.