I'm writing a program that moves docs from one app to another in ApplicationXtender (AX). The AX full-featured client already has such a program (Migration Wizard) that can handle the task, so I created a function that launches it using Process.Start() and supplying the arguments necessary to automate it. When I call the function from a console app or windows form app, the Migration Wizard works perfectly. But the process must be initiated by an event in a web-based workflow project, so I wrote a web service that contains the same function, and then I use an invoke web service control in the workflow to start it. When I consume the function from the web service, the process doesn't complete. I can see it hanging in the task manager. I'm pretty sure it has to do with the user settings in IIS, but I'm not familiar enough with IIS to make any significant difference. I've configured the anonymous authentication's user identity in IIS to launch with a specific user with full rights to AX, and set the DefaultAppPool to run as Local System, but neither worked. I'm thinking I may need to impersonate the user, but I don't know how to do that. Any suggestions?
For reference, here is my code:
Consume Service Code-
Sub Main()
Dim dbName As String
Dim appName As String
Dim preSalesNum As String
Console.WriteLine("Database: ")
dbName = Console.ReadLine
Console.WriteLine("")
Console.WriteLine("Application")
appName = Console.ReadLine
Console.WriteLine("")
Console.WriteLine("Pre-Sales Number:")
preSalesNum = Console.ReadLine
Console.WriteLine("")
MoveDocs.MoveDocs(dbName, appName, preSalesNum)
End Sub
MoveDocs Function (inside of a separate class)-
Public Shared Function MoveDocs(ByVal dbName As String, ByVal appName As String,
ByVal preSalesNum As String) As String
Try
Dim sourceApp As String
If appName = "PRE_SALES_PROJECTS" Then
sourceApp = "PROJECTS"
Else
sourceApp = "LOOSE-FURNITURE"
End If
Dim argsString As String = "/SD " & dbName & " /SU username /SP password /SA
" & appName & " /DD " & dbName & " /DU username /DP password /DA " &
sourceApp & " /S " & """" & preSalesNum & """" & " /A"
Dim procProp As New System.Diagnostics.ProcessStartInfo
With procProp
.FileName = "C:\Program Files (x86)\XtenderSolutions\Content
Management\MigrateWiz32.exe"
.Arguments = argsString
End With
Dim proc As System.Diagnostics.Process =
System.Diagnostics.Process.Start(procProp)
Return argsString
Catch ex As Exception
Return ex.ToString()
End Try
End Function
The MoveDocs() function in the service.asmx file is identical to the one above, minus the 'shared' modifier in the declaration. The app works, the web service doesn't.
ProcessStartInfo has properties for username, password and domain. There's more info on MSDN -
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.password.aspx
Related
I've written a C# application with .net framework. The purpose is to request data from an online spreadsheet app, do stuff with it, then send back updated data.
I think the best way to trigger the exe would be to use webhooks/callbacks, but I gather this would require runnning my program on a web sever with an external IP address.
Rather than periodically polling the spreadsheet app I would like the app to send emails to a specified account upon certain data changes. Upon receiving the email, VBA code checks that the email is from the app then runs the executable.
To run exe on receipt of email:
Private Sub Application_NewMail()
Dim path As String
Dim shl As Variant
path = "C:\Users\***\Desktop\SmartPlugin.exe"
shl = Shell(path, 1)
End Sub
How do I check the sender? The examples I found online loop through all emails but what I'm after is a method of returning the last email received.
Use Application.NewMailEx event instead - it passes the entry id of the new message as the parameter. Use that entry id to call Application.Session.GetItemfromID.
Got it working.
Private Sub Application_NewMail()
Dim objN As NameSpace
Dim objF As MAPIFolder
Set objN = GetNamespace("MAPI")
Set objF = objN.GetDefaultFolder(olFolderInbox)
Set mlItems = objF.Items
mlItems.Sort "CreationTime", True
Set mlItem = mlItems.Item(1)
Dim path As String
Dim shl As Variant
Dim sndString As String
sndString = CStr(mlItem.SenderName)
If InStr(1, sndString, "SmartSheet", vbTextCompare) > 0 Then
path = "C:\Users\Alex Rose\Desktop\SmartPlugin.exe"
shl = Shell(path, 1)
End If
End Sub
I have created following C# console app:
var userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
var path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
var path2 = Environment.ExpandEnvironmentVariables("%userprofile%");
File.AppendAllText(#"D:\logs\info.txt", userName + " -- " + path + " -- " + path2);
When I create a scheduled task using Windows Task Scheduler and set the user account to run the task to my account (ht-laptop\huseyin), I am getting the following output in info.txt file:
ht-laptop\huseyin -- C:\Users\Default\Documents -- C:\Users\Default
This seems to be random though, I had seen cases where the printed text was as follows:
ht-laptop\huseyin -- C:\Users\huseyin\Documents -- C:\Users\huseyin
Any idea why this happens?
Are you running this on Windows 8+ (or similar)? If so, this is a known issue with user profile loading. The technote (kb2968540) has a workaround (which is kind of kludgy IMO).
Is it possible to ask Excel to start a C# method?
How would you implement such a call in Excel?
(i.e. instead of programming in VB, I would like to program in C#)
I can imagine using a VB-macro to start a C# application in the background but maybe you know a nicer way?
For example, the C#-code shall be executed upon a click in a particular Excel cell.
Well you could open a program via VBA. This VBA script gets called by clicking on the Excel-Cell:
var Path = "MYPROGRAMPATH"
var Argument = "MYARGUMENT"
x = Shell("""" & Path & """ """ & Argument & """", vbNormalFocus)
To react on a cell change, use the following event:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'YOUR CODE
End Sub
Then program your C# application and let it determine the arguments.
Your program should react according to the filtered arguments.
This can be done with the Environment.GetCommandLineArgs-Method.
public static void Main()
{
Console.WriteLine();
// Invoke this sample with an arbitrary set of command line arguments.
String[] arguments = Environment.GetCommandLineArgs();
Console.WriteLine("GetCommandLineArgs: {0}", String.Join(", ", arguments));
//Handling of arguments here, switch-case, if-else, ...
}
I've found a solution to the VBA macro:
Sub Button1_Click()
Dim pathStr As String
Dim argumentStr As String
pathStr = "C:/Users/.../Desktop/temp/Trial02.exe"
argumentStr = "Any argument string"
Call Shell("""" & pathStr & """ """ & argumentStr & """", vbNormalFocus)
End Sub
I am fairly new to coding, but have built a few small things. One thing I figured out on my last project was how to run 2 simple commands normally run from a console, but from within a form application instead. Simply, the form had 2 buttons and clicking one caused ipconfig to run and the other ipconfig /all. It then posted the ip information coming from the command into another form I created as a message box. That is important because I am trying to do something similar and nothing is working now.
I have a form that has a spot for user name and a spot for password. On submit, I want it to essentially run the following:
NET USE F: \\ALPHA\CLIENTAPPS /user:domain\%username% %password% /persistent:no
NET USE O: \\ALPHA\USERS /user:domain\%username% %password% /persistent:no
NET USE S: \\ALPHA\COMPANY /user:domain\%username% %password% /persistent:no
Where %username% and %password% are captured from the form and domain will be our actual domain.
Using similar methods to the aforementioned ipconfig program that is working, this is what I came up with. However, when I click the Submit button, nothing happens, no errors, nor does it actually create the network share:
private void btnSubmit_Click(object sender, EventArgs e)
{
string un = txtUsername.Text;
string pw = txtPassword.Text;
System.Diagnostics.ProcessStartInfo PR = new System.Diagnostics.ProcessStartInfo("cmd", #" /c net use W: \\\\ALPHA\\CLIENTAPPS /user:acsdish\\" + un + " " + pw + "/persistent:no");
PR.RedirectStandardOutput = true;
PR.UseShellExecute = false;
PR.CreateNoWindow = true;
System.Diagnostics.Process StartPR = new System.Diagnostics.Process();
StartPR.StartInfo = PR;
StartPR.Start();
}
What am I missing here, or is there a better way? Thanks.
Mike
System.Diagnostics.ProcessStartInfo PR = new System.Diagnostics.ProcessStartInfo("cmd", #" /c net use W: \\\\ALPHA\\CLIENTAPPS /user:acsdish\\" + un + " " + pw + "/persistent:no");
Try to remove "#" or remove escaping of "\" char
Info here (Verbatim string literals)
nothing happens, no errors, nor does it actually create the network share
You've done a lot to ensure that. "No errors" is easy to explain, you don't check for errors nor do you give a way for the user to see them because you made sure that the console window isn't visible. If the command failed that it won't be visible. Checking Process.ExitCode is a minimal requirement.
Next flaw is that you create the mapping to the share for a particular user. Which is fine, drive mappings are a per-user setting. But you are not actually logged-in as that user so you can't see those mappings. You'll have to hit Ctrl+Alt+Del and switch the user account. But that's a lost cause because you passed /persistent:no. That means "persistent while the user is logged in".
Ultimate flaw is that you leave it up to an another process to take care of it. That always loses critical information, especially errors. You should pinvoke the Windows api function that does this so you know when it doesn't work and don't burn a gazillion cycles to run another process. Pinvoke WNetAddConnection2().
For example, I deleted a record on a table on the database and my database is MS Aaccess. Any backup mechanisms that I can refer to? So that when I need a rollback of the database I just restore it quickly from code.
MS Access is the file based database, right? in my understanding, that means, when the connection is closed and the file is not in use, you can copy that file to another location.
Here I assume the application has such privileges on the file system.
Also, I agree with Morten Martner's answer, if the database type is MS SQL Server, then you will definitely need SMO library use.
I'm using the following code to backup SQL server databases:
using System;
using System.Collections.Generic;
using System.Data;
using System.Collections;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using System.Text;
namespace Codeworks.SqlServer.BackupDatabase
{
public class BackupCore
{
public static void Execute( string instance, string database, string outputFile )
{
BackupDeviceItem bdi = new BackupDeviceItem( outputFile, DeviceType.File );
Backup bu = new Backup();
bu.Database = database;
bu.Devices.Add( bdi );
bu.Initialize = true;
// add percent complete and complete event handlers
bu.PercentComplete += new PercentCompleteEventHandler(Backup_PercentComplete);
bu.Complete +=new ServerMessageEventHandler(Backup_Complete);
Server server = new Server( instance );
bu.SqlBackup( server );
}
protected static void Backup_PercentComplete( object sender, PercentCompleteEventArgs e )
{
// Console.WriteLine( e.Percent + "% processed." );
}
protected static void Backup_Complete( object sender, ServerMessageEventArgs e )
{
Console.WriteLine( e.ToString() );
}
}
}
You'll need the management libraries from MS for the correct SQL server version, but those are available for download.
If you're a single user of your database, you just need to close your connection and copy it with the file system.
If there are multiple users, then you should use a different method. If you actually have Access available, there's an undocumented command that will make a backup of the tables a Jet/ACE file:
Application.SaveAsText 6, vbNullString, strTargetMDB
Now, since this can only be done with the database open in the Access UI, it requires automating Access and operating on the CurrentDB. Here's an implementation that runs within Access:
Public Function CreateBackup(strMDBName As String, strBackupPath As String, _
Optional bolCompact As Boolean = False) As Boolean
On Error GoTo errHandler
Dim objAccess As Object
Dim strBackupMDB As String
Dim strCompactMDB As String
If Len(Dir(strBackupPath & "\*.*")) = 0 Then ' alternative: use File System Object for this
MkDir strBackupPath
End If
Set objAccess = New Access.Application
objAccess.Application.OpenCurrentDatabase strMDBName
strBackupMDB = "Backup" & Format(Now(), "YYYYMMDDhhnnss") & ".mdb"
Debug.Print strBackupPath & "\" & strBackupMDB
objAccess.Application.SaveAsText 6, vbNullString, strBackupPath & "\" & strBackupMDB
objAccess.Application.Quit
Set objAccess = Nothing
If bolCompact Then
strCompactMDB = strBackupPath & "\" & "c_" & strBackupMDB
Name strBackupPath & "\" & strBackupMDB As strCompactMDB
DBEngine.CompactDatabase strCompactMDB, strBackupPath & "\" & strBackupMDB
Kill strCompactMDB
End If
CreateBackup = (Len(Dir(strBackupPath & "\" & strBackupMDB)) > 0)
exitRoutine:
If Not (objAccess Is Nothing) Then
On Error Resume Next
objAccess.Application.Quit
On Error GoTo 0
Set objAccess = Nothing
End If
Exit Function
errHandler:
Select Case Err.Number
Case 75 ' Path/File access error -- tried to MkDir a folder that already exists
Resume Next
Case Else
MsgBox Err.Number & ": " & Err.Description, vbExclamation, "Error in CreateBackup()"
Resume exitRoutine
End Select
End Function
To run that from C# you'd have to automate Access, and you likely don't want a dependency on Access.
Since I work in Access exclusively, that's the method I use, so I've never programmed the more complicated methods.
If you have exclusive access to the database, you could use JRO CompactDatabase command to compact to a new filename, but if you have exclusive access, you can also use the file system.
So, basically, you've got choices about how to export the data tables to a backup database. You could use DoCmd.TransferDatabase to copy all the data tables, and then copy the relationships, or you could create an empty template database and append the data from each table in turn to a copy of the template (in an order that won't violate RI, of course).
Neither of those sounds anything but messy to me, and that's why I use the SaveAsText method! But if I wasn't running Access, the other two alternatives would be worth doing.