Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
Stefan-Schnell
Active Contributor
1,228
If you work with PowerShell in ABAP context it could be profitably to integrate PowerShell development into your ABAP development process. In this example I will show how to use PowerShell ISE to read and store PowerShell scripts as includes on an SAP system.

At the SAP system I added a call and three RFMs. At first the class to handle accesses to the PowerShell includes. The class contains four methods:

  1. get_incl delivers a list of include names.
    It looks in table TRDIR and delivers all include entries of the given pattern.

  2. read_incl reads an include.
    At first it checks in the table TRDIR if the include exists. Then it reads the source and delivers it as string.

  3. write_incl writes an include.
    At first it checks in the table TRDIR if the include exists. Then it splits the passed string at crlf and stores it as include.

  4. if_http_extension~handle_request makes it possible to use this class as web service too.


CLASS z_cl_posh_handle_incl DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .

PUBLIC SECTION.

interfaces IF_HTTP_EXTENSION .

"! Detects Includes
"! @parameter iv_inclpattern | Detection pattern
"!
"! @parameter rt_incl | List of includes
"!
"! @exception not_authorized | No authorization
METHODS get_incl
IMPORTING
VALUE(iv_inclpattern) TYPE sobj_name
RETURNING
VALUE(rt_incl) TYPE rslpo_th_objnm
EXCEPTIONS
not_authorized
.

"! Reads an Include
"! @parameter iv_inclname | Name of the include
"!
"! @parameter rv_strincl | Include as string
"!
"! @exception not_authorized | No authorization
METHODS read_incl
IMPORTING
VALUE(iv_inclname) TYPE sobj_name
RETURNING
VALUE(rv_incl) TYPE string
EXCEPTIONS
not_authorized
.

"! Writes an Include
"! @parameter iv_inclname | Name of the include
"! @parameter iv_strincl | Include as string
"!
"! @parameter rv_result | Return value, 1 for success, otherwise 0
"!
"! @exception not_authorized | No authorization
METHODS write_incl
IMPORTING
VALUE(iv_inclname) TYPE sobj_name
VALUE(iv_incl) TYPE string
RETURNING
VALUE(rv_result) TYPE integer
EXCEPTIONS
not_authorized
.

PROTECTED SECTION.

PRIVATE SECTION.

ENDCLASS.



CLASS z_cl_posh_handle_incl IMPLEMENTATION.



METHOD if_http_extension~handle_request."-----------------------------

DATA:
lv_verb TYPE string,
lv_operation TYPE string,
lv_inclpattern TYPE sobj_name,
lt_incl TYPE rslop_th_objnm,
lv_inclname TYPE sobj_name,
lv_incl TYPE string
.

lv_verb = server->request->get_header_field( name = '~request_method' ).
CASE lv_verb.
WHEN 'GET'.
lv_operation = server->request->get_header_field( name = 'operation' ).
CASE lv_operation.

WHEN 'get'."-get_incl-----------------------------------------

lv_inclpattern = server->request->get_header_field( name = 'inclpattern' ).
CHECK lv_inclpattern IS NOT INITIAL.
TRY.
lt_incl = me->get_incl( iv_inclpattern = lv_inclpattern ).
CATCH cx_root.
CALL METHOD server->response->set_status(
code = 403
reason = 'Error - Not authorized' ).
RETURN.
ENDTRY.

IF lt_incl IS INITIAL.
CALL METHOD server->response->set_status(
code = 204
reason = 'No Response' ).
ELSE.
CALL METHOD server->response->set_cdata(
data = /ui2/cl_json=>serialize( data = lt_incl )
).
CALL METHOD server->response->set_status( code = 200 reason = 'Ok' ).
ENDIF.

WHEN 'read'."-read_incl---------------------------------------

lv_inclname = server->request->get_header_field( name = 'inclname' ).
CHECK lv_inclname IS NOT INITIAL.
TRY.
lv_incl = me->read_incl( iv_inclname = lv_inclname ).
CATCH cx_root.
CALL METHOD server->response->set_status(
code = 403
reason = 'Error - Not authorized' ).
RETURN.
ENDTRY.

IF lv_incl IS INITIAL.
CALL METHOD server->response->set_status(
code = 204
reason = 'No Response' ).
ELSE.
CALL METHOD server->response->set_cdata( data = lv_incl ).
CALL METHOD server->response->set_status( code = 200 reason = 'Ok' ).
ENDIF.

ENDCASE.

WHEN 'POST'."-write_incl------------------------------------------

lv_inclname = server->request->get_header_field( name = 'inclname' ).
CHECK lv_inclname IS NOT INITIAL.
lv_incl = server->request->get_cdata( ).
CHECK lv_incl IS NOT INITIAL.
TRY.
CASE me->write_incl( iv_inclname = lv_inclname iv_incl = lv_incl ).
WHEN 0.
CALL METHOD server->response->set_status( code = 500 reason = 'Error - Can not write' ).
WHEN 1.
CALL METHOD server->response->set_status( code = 200 reason = 'Ok' ).
ENDCASE.
CATCH cx_root.
CALL METHOD server->response->set_status(
code = 403
reason = 'Error - Not authorized' ).
RETURN.
ENDTRY.

WHEN OTHERS.
CALL METHOD server->response->set_status(
code = 400
reason = 'Error - Verb not supported' ).

ENDCASE.

ENDMETHOD.



METHOD get_incl."-----------------------------------------------------

AUTHORITY-CHECK OBJECT 'S_DEVELOP'
ID 'ACTVT' FIELD '03'.
IF sy-subrc <> 0.
RAISE not_authorized.
ENDIF.

CHECK iv_inclpattern IS NOT INITIAL.
SELECT name FROM trdir INTO TABLE rt_incl WHERE name LIKE iv_inclpattern AND
subc = 'I' AND appl = SPACE.

ENDMETHOD.



METHOD read_incl."----------------------------------------------------

DATA:
lt_trdir TYPE trdir,
lt_incl TYPE TABLE OF string,
lv_inclline TYPE string,
lv_retincl TYPE string
.

AUTHORITY-CHECK OBJECT 'S_DEVELOP'
ID 'ACTVT' FIELD '03'.
IF sy-subrc <> 0.
RAISE not_authorized.
ENDIF.

SELECT SINGLE * FROM trdir INTO lt_trdir WHERE name = iv_inclname AND
subc = 'I' AND appl = SPACE.
CHECK sy-subrc = 0.
READ REPORT iv_inclname INTO lt_incl.
CHECK sy-subrc = 0.
LOOP AT lt_incl INTO lv_inclline.
lv_retincl = lv_retincl && lv_inclline && cl_abap_char_utilities=>cr_lf.
CLEAR lv_inclline.
ENDLOOP.
rv_incl = lv_retincl.

ENDMETHOD.



METHOD write_incl."---------------------------------------------------

DATA:
lt_trdir TYPE trdir,
lt_incl TYPE TABLE OF string
.

AUTHORITY-CHECK OBJECT 'S_DEVELOP'
ID 'ACTVT' FIELD '02'.
IF sy-subrc <> 0.
RAISE not_authorized.
ENDIF.

rv_result = 0.
CHECK iv_inclname+0(1) = 'Y' OR iv_inclname+0(1) = 'Z'.
SELECT SINGLE * FROM trdir INTO lt_trdir WHERE name = iv_inclname AND
subc = 'I' AND appl = space.
CHECK sy-subrc = 0.
SPLIT iv_incl AT cl_abap_char_utilities=>cr_lf INTO TABLE lt_incl.
INSERT REPORT iv_inclname FROM lt_incl PROGRAM TYPE 'I'.
CHECK sy-subrc = 0.
rv_result = 1.

ENDMETHOD.



ENDCLASS.

Here the RFM to read an include.
FUNCTION z_rfc_readincl
IMPORTING
VALUE(IV_INCLNAME) TYPE SOBJ_NAME
EXPORTING
VALUE(EV_STRINCL) TYPE STRING
EXCEPTIONS
NOT_AUTHORIZED.

DATA:
lo_incl TYPE REF TO /gkv/qm90_cl_posh_handle_incl
.

CREATE OBJECT lo_incl.

TRY.
ev_strincl = lo_incl->read_incl( iv_inclname = iv_inclname ).
CATCH cx_root.
RAISE not_authorized.
ENDTRY.

ENDFUNCTION.

Here the RFM to write an include.
FUNCTION z_rfc_writeincl
IMPORTING
VALUE(iv_inclname) TYPE sobj_name
VALUE(iv_strincl) TYPE string
EXPORTING
VALUE(ev_result) TYPE integer
EXCEPTIONS
not_authorized.

DATA:
lo_incl TYPE REF TO /gkv/qm90_cl_posh_handle_incl
.

CREATE OBJECT lo_incl.

TRY.
ev_result = lo_incl->write_incl( iv_inclname = iv_inclname iv_incl = iv_strincl ).
CATCH cx_root.
RAISE not_authorized.
ENDTRY.

ENDFUNCTION.

Here the PowerShell script to use this RFMs. The first function Read-SAPInclude uses z_rfc_readincl and the second function Write-SAPInclude uses z_rfc_writeincl. The basics to use NCo with PowerShell is described here and here.
Function Read-SAPInclude {
#-Begin-----------------------------------------------------------------

#-Function Get-Destination--------------------------------------------
Function Get-Destination {

#-Verbindungsparamter-----------------------------------------------
$cfgParams = `
New-Object SAP.Middleware.Connector.RfcConfigParameters
$cfgParams.Add($cfgParams::Name, "TEST")
$cfgParams.Add($cfgParams::AppServerHost, "ABAP")
$cfgParams.Add($cfgParams::SystemNumber, "00")
$cfgParams.Add($cfgParams::Client, "001")
$cfgParams.Add($cfgParams::User, "BCUSER")

$SecPasswd = Read-Host -Prompt "Passwort" -AsSecureString
$ptrPasswd = `
[Runtime.InteropServices.Marshal]::SecureStringToBStr($SecPasswd)
$Passwd = `
[Runtime.InteropServices.Marshal]::PtrToStringBStr($ptrPasswd)
$cfgParams.Add($cfgParams::Password, $Passwd)

Return [SAP.Middleware.Connector.RfcDestinationManager]::GetDestination($cfgParams)

}

#-Sub ReadInclude-----------------------------------------------------
Function ReadInclude([string]$InclName) {

$destination = Get-Destination

$rfcFunction = $destination.Repository.CreateFunction("Z_RFC_READINCL")
$rfcFunction.SetValue("IV_INCLNAME", $InclName)
$rfcFunction.Invoke($destination)
$NewTab = $psISE.CurrentPowerShellTab.Files.Add()
$NewTab.Editor.Text = $rfcFunction.GetValue("EV_STRINCL")
$NewTab.Editor.SetCaretPosition(1,1)

}

#-Sub Main------------------------------------------------------------
Function Main () {

[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')

$InclName = [Microsoft.VisualBasic.Interaction]::InputBox("Include Name", `
"Request")
If (-not ([string]::IsNullOrEmpty($InclName))) {
ReadInclude $InclName.ToUpper()
}

}

#-Main----------------------------------------------------------------
Main

#-Error routine-------------------------------------------------------
Trap {
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") > $Null
[Void] [System.Windows.Forms.MessageBox]::Show( `
$_.Exception.GetType().FullName + `
[System.Environment]::NewLine + `
"Error at line " + $_.InvocationInfo.ScriptLineNumber + `
" in " + $_.InvocationInfo.ScriptName + `
[System.Environment]::NewLine + [System.Environment]::NewLine + `
$_.Exception.Message, "An Error Occurred", 0)
Exit
}

#-End-------------------------------------------------------------------
}

Function Write-SAPInclude {
#-Begin-----------------------------------------------------------------

#-Function Get-Destination--------------------------------------------
Function Get-Destination {

#-Verbindungsparamter-----------------------------------------------
$cfgParams = `
New-Object SAP.Middleware.Connector.RfcConfigParameters
$cfgParams.Add($cfgParams::Name, "TEST")
$cfgParams.Add($cfgParams::AppServerHost, "ABAP")
$cfgParams.Add($cfgParams::SystemNumber, "00")
$cfgParams.Add($cfgParams::Client, "001")
$cfgParams.Add($cfgParams::User, "BCUSER")

$SecPasswd = Read-Host -Prompt "Passwort" -AsSecureString
$ptrPasswd = `
[Runtime.InteropServices.Marshal]::SecureStringToBStr($SecPasswd)
$Passwd = `
[Runtime.InteropServices.Marshal]::PtrToStringBStr($ptrPasswd)
$cfgParams.Add($cfgParams::Password, $Passwd)

Return [SAP.Middleware.Connector.RfcDestinationManager]::GetDestination($cfgParams)

}

#-Sub WriteInclude----------------------------------------------------
Function WriteInclude([string]$InclName) {

$destination = Get-Destination

$rfcFunction = $destination.Repository.CreateFunction("Z_RFC_WRITEINCL")
$rfcFunction.SetValue("IV_INCLNAME", $InclName)
$rfcFunction.SetValue("IV_STRINCL", $psISE.CurrentFile.Editor.Text)
$rfcFunction.Invoke($destination)
$Result = $rfcFunction.GetValue("EV_RESULT")
If ($Result = 0) {
Write-Host "An Error Occurred"
}

}

#-Sub Main------------------------------------------------------------
Function Main () {

[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')

$InclName = [Microsoft.VisualBasic.Interaction]::InputBox("Include Name", `
"Request")
If (-not ([string]::IsNullOrEmpty($InclName))) {
WriteInclude $InclName.ToUpper()
}

}

#-Main----------------------------------------------------------------
Main

#-Error routine-------------------------------------------------------
Trap {
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") > $Null
[Void] [System.Windows.Forms.MessageBox]::Show( `
$_.Exception.GetType().FullName + `
[System.Environment]::NewLine + `
"Error at line " + $_.InvocationInfo.ScriptLineNumber + `
" in " + $_.InvocationInfo.ScriptName + `
[System.Environment]::NewLine + [System.Environment]::NewLine + `
$_.Exception.Message, "An Error Occurred", 0)
Exit
}

#-End-------------------------------------------------------------------
}

#-Sub Load-NCo----------------------------------------------------------
Function Load-NCo {

[String]$ScriptDir = "C:\Dummy"

If ([Environment]::Is64BitProcess) {
[String]$Path = $ScriptDir + "\x64\"
} Else {
[String]$Path = $ScriptDir + "\x86\"
}

[String]$File = $Path + "sapnco.dll"; Add-Type -Path $File
$File = $Path + "sapnco_utils.dll"; Add-Type -Path $File

}

#-Main------------------------------------------------------------------
Load-NCo
$null = $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Read SAP Include", { Read-SAPInclude }, $null)
$null = $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Write SAP Include", { Write-SAPInclude }, $null)

Store this script into your profile file. Type into your PS console $profile.CurrentUserCurrentHost to find the location of the file. If the file exists add the code, otherwise store the code into this file.



If you restart your ISE you have now two new menu items.



The first menu item reads an include from an SAP system, opens a new tab at PowerShell ISE and writes the include from the SAP system to the PowerShell ISE window. The second menu item writes the code from the open PowerShell ISE window as include into an SAP system. In both cases you must define the include name and the password of the system. The system, client and user are hard coded, you can add an own login screen if you like.





After that you have on the one hand a new tab with the source of the include file.





And on the other hand you have new content in the include.





 

Last but not least a look on Postman test environment, which uses the class via HTTP request. On this way is it possible to use this functionality without any additional library.



 

This example shows how easy it is to integrate PowerShell and ABAP development.

Enjoy polyglot programming.
Labels in this area