TwinCAT - How to check contents of a directory from Beckhoff PLC - plc

I am trying to perform file management within the PLC. Currently, NT_StartProcess works as follows, but I will not have any feedback after the process has been spawned. Is there a way to check the contents of a directory from the PLC? Is there any way to get feedback from NT_StartProcess?
// File Locations
sTargetFilePath := 'C:\LocalHistory\test.job';
sTargetDirectory := 'C:\\CustomerDir';
// Build Command String
sCommand := '/C '; // Special command indicating command string input
sCommand := CONCAT(sCommand, 'move '); // Add move command
sCommand := CONCAT(sCommand, sTargetFilePath); // Add target file
sCommand := CONCAT(sCommand, ' '); // Required space for command
sCommand := CONCAT(sCommand, sTargetDirectory); // Add target location
// Output -> ‘/C move C:\NET-DRIVE\NewOrders\Original.xml 'C:\NET-DRIVE\OldOrders’
Process(
NETID := '', // Local System
PATHSTR := 'C:\Windows\System32\cmd.exe', // Path to local cmd executable
COMNDLINE := sCommand, // Comnmand to be executed
ERR => bError, // Error Output
ERRID => iErrorId // Error Id Output
);
// Trigger Command
IF bTrigger THEN
bTrigger := FALSE;
Process(START:=TRUE);
Process(START:=FALSE);
END_IF

Yes, there is a way. What you are looking for are those two function blocks: FB_EnumFindFileList, FB_EnumFindFileEntry
As for feedback from NT_StartProcess, you can't get it directly. There are two workaround I've been using:
Write the results to a text file, and open this file using TwinCAT.
In your case it would be something like this:
‘/C move C:\NET-DRIVE\NewOrders\Original.xml 'C:\NET-DRIVE\OldOrders && echo DONE > out.txt || echo FAIL > out.txt’
Command after && will be executed only, if previous succeded. Command after || will be executed only if previous failed.
> operator writes output of previous command to a file
Example above should create a file out.txt and write DONE or FAIL inside. I don't have a PLC with TwinCAT with me at the moment, but it works in windows cmd.
Write a program (In C# for example) that does what needs to be done and then connects to TwinCAT via ADS to put the result in variables

Related

How to open a website using AutohotKey that contains special characters "%" in the url itself?

I want to open this website with this line of code:
Run,https://logowanie.uek.krakow.pl/cas/login?service=https%3A%2F%2Fe-uczelnia.uek.krakow.pl%2Flogin%2Findex.php%3FauthCAS%3DCAS
When i try to run this line I get this error:
The following variable name contains an illegal character:
'2Fe-uczelnia-uek.krakow.pl'
The program will exit.
Im pretty sure that "%" is the issue.
You dont need to encode URL while using Run command.
Run,https://logowanie.uek.krakow.pl/cas/login?service=https://e-uczelnia.uek.krakow.pl/login/index.php?authCAS=CAS
But if you really need or want to encode, you may try this function:
str := "hello%20world"
MsgBox, % decoded := EncodeDecodeURI(str, false)
MsgBox, % EncodeDecodeURI(decoded)
EncodeDecodeURI(str, encode := true, component := true) {
static Doc, JS
if !Doc {
Doc := ComObjCreate("htmlfile")
Doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
JS := Doc.parentWindow
( Doc.documentMode < 9 && JS.execScript() )
}
Return JS[ (encode ? "en" : "de") . "codeURI" . (component ? "Component" : "") ](str)
}
Source: https://www.autohotkey.com/boards/viewtopic.php?t=84825
You want to ditch the usage of legacy syntax, and enter the modern expression syntax by specifying a single % at the start of that parameter.
Then you can explicitly specify a string with "":
Run, % "https://logowanie.uek.krakow.pl/cas/login?service=https%3A%2F%2Fe-uczelnia.uek.krakow.pl%2Flogin%2Findex.php%3FauthCAS%3DCAS"
If you for some reason were to want to use the legacy syntax, (there is no valid reason to do so), you'd want to escape the %s with a `.

console output using shellexecuteW

I'm using ShellExecuteW from shell32.dll:
int value= ShellExecuteW(0, "open", "C:\test.bat", strParameters, "", 1);
The batch file runs a java app which seems to open but returns an error and quickly the console window closes.
I want to capture the error from the console. I've tried adding the following re-directions at the end of my command in the batch file:
> output.txt
2> output.txt
> output.txt 2>&1
1> output.txt 2>&1
| output.txt
I would expect these common commands to work but none of them result in anything being written in output.txt. What could I be doing wrong here?
I'm using Metatrader5 (MQL5 language) to call shellexecuteW from.
Thankyou for your replies.
First you should put the bat file in Terminal path in "File" folder because of mql5 security policy, Secondly put these codes in your script to use "ShellExecuteW" function correctly.
#import "shell32.dll"
int ShellExecuteW(int hwnd,string operation,string file,string parameters,string directory,int showCmd);
#import
int result;
string strParameters = "";
string filename="test.bat";
string Batfile_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\Files\\"+filename;
Print("Batfile_path:",Batfile_path);
result = ShellExecuteW(0, "open", Batfile_path, strParameters, "", 0);
if(result <= 32)Print("Shell Execute for running bat file Failed: ", result);

How to prevent using anything except application in Delphi XE

I'm developing a VCL Form Application
When the application started
I need to prevent the user of the computer to do anything
He can't close the application by ALT+F4 or CTRL+ALT+DEL
He can't change to another window by ALT+Tab
He can't Go to the desktop by clicking Windows + D
This will used in Cyber Cafe and it is a Server/Client application
so before the Admin in the Server giving access to the Client, He can't do anything in the computer .. just a full screen for my form
Because it is the login system of Windows that traps the CTRL-ALT-DEL combination (and so it is not accessible to user applications), you will need to change the system's keyboard scancode map to ignore at least one of those keys.
First you have to ensure Autologon on your computer, else you can login. This could also be done in the registry.
Then the tricky part editing the scancode map. In MSDN you'll find an article about how to do: https://msdn.microsoft.com/en-us/library/windows/hardware/jj128267%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
I've found this reg file that is suppose to disable CTRL + ALT + DELETE but I havent tested it
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"Scancode Map"=hex:00,00,00,00,00,00,00,00,03,00,00,00,00,00,38,00,00,00,38,e0,\
00,00,00,00
Be aware that this is extremely hirisk. Both using the REG file and changing the scancode map. I suggest you test your stuff in a virtual Machine.
You also need to prevent the user from shuttingdown the computer. Which is also done in registry:
User Key: [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\
Explorer]
System Key: [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\
Explorer]
Value Name: NoClose
Data Type: REG_DWORD (DWORD Value)
Value Data: (0 = shutdown enabled, 1 = shutdown disabled)
So inorder for shuttingdown the computer your probram must make a call to ShutdownwindowsEx
function ExitWindows(iFlags: Integer): Boolean;
var
osVerInfo: TOSVersionInfo;
function SetPrivilege(sPrivilegeName: string; bEnabled: Boolean): Boolean;
var
TPPrev, TP: TTokenPrivileges;
Token: THandle;
dwRetLen: DWord;
begin
Result := False;
OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, Token);
TP.PrivilegeCount := 1;
if (LookupPrivilegeValue(nil, PChar(sPrivilegeName), TP.Privileges[0].LUID)) then
begin
if (bEnabled) then
TP.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED
else
TP.Privileges[0].Attributes := 0;
dwRetLen := 0;
Result := AdjustTokenPrivileges(Token, False, TP, sizeof(TPPrev), TPPrev, dwRetLen);
end;
CloseHandle(Token);
end;
begin
Result := True;
osVerInfo.dwOSVersionInfoSize := sizeof(TOSVersionInfo);
if GetVersionEx(osVerInfo) then
case osVerInfo.dwPlatformId of
VER_PLATFORM_WIN32_WINDOWS:
if not ExitWindowsEx(iFlags, 0) then
Result := False; // handle errors...
VER_PLATFORM_WIN32_NT:
if SetPrivilege('SeShutdownPrivilege', True) then
begin
if not ExitWindowsEx(iFlags, 0) then
Result := False; // handle errors...
SetPrivilege('SeShutdownPrivilege', False)
end
else
Result := False; // handle errors...
else
Result := False;
end;
end;

How to execute mongo shell command from javascript

In a mongo shell window, I'd like to periodically run a script that will display various stats on the database activity, before displaying the stats, I'd like to clear the screen. There is a "cls" command in the mongo shell, but I am not able to execute it from within the javascript.
function stats () {
while(1) {
cls;
print("display stats");
sleep(5000);
}}
The line with the "cls" is not recognized.
Thank you for any suggestions,
Gary
At the first glance it seemed that you won't be able to do it. According to the docs here: "You cannot use any shell helper (e.g. use , show dbs, etc.) inside the JavaScript file because they are not valid JavaScript.".
One option was to fill the screen with empty lines:
function clearIt () { for(var i = 0; i < 100; i++) { print() } }
clearIt()
However, thanks to #NeilLunn pointing it out there seems to be a solution:
function clearIt () { run('clear') }
clearIt()
This would execute system command which will clear your terminal screen. I don't know how reliable it is (see man clear -> depends if it can figure out how to clear screen) and this works only on POSIX systems. On Windows you would have to replace clear with cls:
function clearIt () { run('cls') }
Additional:
I looked up the source code of mongo shell (src/mongo/shell/linenoise.cpp). Here is how it clears the screen:
void linenoiseClearScreen( void ) {
#ifdef _WIN32
COORD coord = {0, 0};
CONSOLE_SCREEN_BUFFER_INFO inf;
HANDLE screenHandle = GetStdHandle( STD_OUTPUT_HANDLE );
GetConsoleScreenBufferInfo( screenHandle, &inf );
SetConsoleCursorPosition( screenHandle, coord );
DWORD count;
FillConsoleOutputCharacterA( screenHandle, ' ', inf.dwSize.X * inf.dwSize.Y, coord, &count );
#else
if ( write( 1, "\x1b[H\x1b[2J", 7 ) <= 0 ) return;
#endif
}
In case you feel like trying to implement your own screen cleaning function by filling screen with chars.
> help admin
ls([path]) list files
pwd() returns current directory
listFiles([path]) returns file list
hostname() returns name of this host
cat(fname) returns contents of text file as a string
removeFile(f) delete a file or directory
load(jsfilename) load and execute a .js file
run(program[, args...]) spawn a program and wait for its completion
runProgram(program[, args...]) same as run(), above
sleep(m) sleep m milliseconds
getMemInfo() diagnostic
This shows the run and runProgram commands along with some other helpers. The program argument is a string.

InnoSetup Dynamic ComboBox, check which item is selected and execute program

In InnoSetup I want to disply a ComboBox on the Finished Page which shows the Components that were installed.
You can choose "None" or any of the installed Components and start the associated program when clicking on finish.
This is my code so far:
procedure CurPageChanged(CurPageID: Integer);
var
NewComboBox1: TNewComboBox;
begin
if (CurPageID = wpFinished) then begin
NewComboBox1 := TNewComboBox.Create(WizardForm);
with NewComboBox1 do begin
Parent := WizardForm.FinishedPage;
Left := ScaleX(256);
Top := ScaleY(208);
Width := ScaleX(145);
Height := ScaleY(21);
ItemIndex := 0;
Style := csDropDownList;
Items.Add('None');
if IsComponentSelected('1') then
Items.Add('Component 1');
if IsComponentSelected('2') then
Items.Add('Component 2');
if IsComponentSelected('3') then
Items.Add('Component 3');
end;
end;
end;
First I want to set "None" as automatically selected. when the page is shown. I have looked up many Pascal forums but none of the solutions worked, like NewComboBox1.ItemSelected=0 (or similar, don't remember correctly...). So how do I achieve this?
Then I don't know how to make a program start when clicking on Finish. I thought
function NextButtonClick
might help but then no Next button worked in the setup.
Maybe there is also a problem because the list is created depending on which Components were selected, so item 1 is not Component 1, if Component 1 was not selected but Component 2 for instance.
I thought one might solve this by making the items invisible instead of not creating them at all.
I looked in the Support Classes Reference in the IS help file but didn't find anything that would help me.
I am looking forward to your answers!
There's no simple way to do this due to a lack of missing access to the file name and destination directory which the component is binded to. Even TSetupComponentEntry internal record doesn't contain this information, but even if would, you won't be able to access it. So, the following script uses its own separate array which contains the component/file linkage needed for this task:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Components]
Name: "program_32"; Description: "Program 32-bit"
Name: "program_x64"; Description: "Program 64-bit"
Name: "program_ia64"; Description: "Program IA 64-bit"
[Files]
Source: "MyProg.exe"; DestDir: "{app}"; Components: program_32
Source: "MyProg-x64.exe"; DestDir: "{app}"; Components: program_x64
Source: "MyProg-IA64.exe"; DestDir: "{app}"; Components: program_ia64
[Code]
type
TFileData = record
Component: string;
Description: string;
FileName: string;
Parameters: string;
end;
var
ComponentCombo: TNewComboBox;
ComponentArray: array of TFileData;
SelectionArray: array of TFileData;
procedure InitializeWizard;
begin
// this is a weakness of this solution - you need to fill the array
// of components that can be added to the final combo box when they
// are selected on component selection page. This is needed because
// you can't get neither file name nor destination directory of the
// file for the component from script. As first, set how many items
// you want to add to your component array storage
SetArrayLength(ComponentArray, 2);
// the Component member must match to the "Name" parameter from the
// [Components] section item since it's used in IsComponentSelected
// function call
ComponentArray[0].Component := 'program_32';
// the Description member is the text displayed in the combo item
ComponentArray[0].Description := 'Program 32-bit';
// the FileName member is the name of the file including path. This
// member may contain InnoSetup constants
ComponentArray[0].FileName := '{app}/MyProg.exe';
// the Parameters member contains execution parameters
ComponentArray[0].Parameters := '-a';
// this is the second item that can be added to the combo box, note
// that the program_ia64 component is not added to this array, what
// means, that it cannot be added to the "run" combo box. It's such
// kind of a filter for components like help files etc.
ComponentArray[1].Component := 'program_x64';
ComponentArray[1].Description := 'Program 64-bit';
ComponentArray[1].FileName := '{app}/MyProg-x64.exe';
ComponentArray[1].Parameters := '-b';
end;
procedure CurPageChanged(CurPageID: Integer);
var
I: Integer;
begin
if (CurPageID = wpFinished) then
begin
ComponentCombo := TNewComboBox.Create(WizardForm);
ComponentCombo.Parent := WizardForm.FinishedPage;
ComponentCombo.Left := ScaleX(256);
ComponentCombo.Top := ScaleY(208);
ComponentCombo.Width := ScaleX(145);
ComponentCombo.Height := ScaleY(21);
ComponentCombo.Style := csDropDownList;
ComponentCombo.Items.Add('None');
for I := 0 to GetArrayLength(ComponentArray) - 1 do
if IsComponentSelected(ComponentArray[I].Component) then
begin
ComponentCombo.Items.Add(ComponentArray[I].Description);
SetArrayLength(SelectionArray, GetArrayLength(SelectionArray) + 1);
SelectionArray[High(SelectionArray)] := ComponentArray[I];
end;
ComponentCombo.ItemIndex := 0;
end;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
var
FileData: TFileData;
ResultCode: Integer;
begin
Result := True;
if (CurPageID = wpFinished) and (ComponentCombo.ItemIndex > 0) then
begin
FileData := SelectionArray[ComponentCombo.ItemIndex - 1];
Exec(ExpandConstant(FileData.FileName), FileData.Parameters, '', SW_SHOW,
ewNoWait, ResultCode);
end;
end;