Skip to Content

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.

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply