---
rexxpub:
  defaults: rony.yaml
  logo: oorexx
---

<!-- 110_AutoWin_V21.odp (2026-10-15 09:38) -->

<!-- slide # 1 (2026-10-15 09:38) ----------------------------- ODP slide # 1 -->
. slide cover
# Windows-Automatisation 1

. subtitle
**OLE-Automation/ActiveX-Automation, ooRexx Proxy Class "OLEObject"**

. cover-art
. picture img/110_AutoWin_V21/p01-diagram-7.png

<!-- slide # 2 (2026-10-15 09:38) ----------------------------- ODP slide # 2 -->
# OLE (ActiveX) Automation

. after
- **COM**: **C**omponent **O**bject **M**odel
    . after
    - RPC ("remote procedure call")
    . click
    - Defines standard ***i**nterface* function "***I***Unknown"
    . click
    - Developed further
        - DCOM, COM+
. click
- **OLE** : **O**bject **L**inking and **E**mbedding
    . after
    - COM-based, ***i**nterface* function "***I***Dispatch"
    . click
    - Linking of documents (dynamic data exchange, DDE)
        - Cold link
        - Warm link
        - Hot link
    . click
    - Embedding of alien/foreign documents

<!-- slide # 3 (2026-10-15 09:38) ----------------------------- ODP slide # 3 -->
# OLE (ActiveX) Automation

. steps each=after
- VBX, OCX, ActiveX
    - Set of COM-Interfaces for defining Windows "components"
        - Windows programs, which can be combined as building stones
        - Defined interface for communicating with components
    . click
    - Acronyms
        - Visual Basic Extension
            - Mostly developed for GUI
        - Object Component Extension and ActiveX
            - Independent of Visual Basic, therefore usable for all Windows programs

<!-- slide # 4 (2026-10-15 09:38) ----------------------------- ODP slide # 4 -->
. slide reference="Cf. winextensions.pdf (8. OLE Automation)"
# OLE (ActiveX) Automation

. after
- OLE (ActiveX) Automation
    . after
    - Interface for remotely controlling Windows applications/components
    . click
    - Set of COM-based interface functions
        . after
        - Standardized definition of programming interfaces for (scripting) languages
            - Invocation of *methods* (functions) in a Windows program/component
            - Querying and setting *property* ("attribute") values of a Windows program/component
            - Intercepting of *events* that get raised by a Windows program/component
            - Querying of *constant* values in a Windows program/component
        . click
        - Logging of user actions, which can be transformed into a "script" ("macro") program

<!-- slide # 5 (2026-10-15 09:38) ----------------------------- ODP slide # 5 -->
. slide reference="Cf. winextensions.pdf (8. OLE Automation)" presenter="Till Winkler"
# OLE (ActiveX) Automation

. columns 1 11

<div style="height:213px"></div>

**COM Class Provider**


. next column

. drawing id=#p5d15 ratio=2.16 width=100% size=0.91
. group box dashed id=#p5x17 at=0,16.8,100,83.2 style="font-size: 1.00em"

. end group
. box id=#p5x18 at=16.7,41.3,66.1,8.8 style="--skin-box-bg: #ffffff; font-size: 1.61em"
Collection of COM-Classes

. box id=#p5x20 at=3.5,64.1,12.2,10.5 style="--skin-box-bg: #ffffff; font-size: 1.21em"
Internet Explorer

. box id=#p5x21 at=16.9,64.1,12.2,10.5 style="--skin-box-bg: #ffffff; font-size: 1.21em"
Word

. box id=#p5x22 at=30.3,64.1,12.2,10.5 style="--skin-box-bg: #ffffff; font-size: 1.21em"
Excel

. box id=#p5x23 at=43.7,64.1,12.2,10.5 style="--skin-box-bg: #ffffff; font-size: 1.21em"
PowerPoint

. box id=#p5x24 at=57.1,64.1,12.2,10.5 style="--skin-box-bg: #ffffff; font-size: 1.21em"
...

. box id=#p5x25 at=70.4,64.1,12.2,10.5 style="--skin-box-bg: #ffffff; font-size: 1.21em"
AutoCAD

. group box id=#p5x26 at=83.8,64.1,12.2,10.5 style="--skin-box-bg: #ffffff; font-size: 1.01em"
And many

others ...

. end group
. box square id=#p5x27 at=5.9,75.5,8.1,4.5 style="--skin-box-bg: #ffffff"
methods

. box square id=#p5x28 at=5.9,80.8,8.1,4.5 style="--skin-box-bg: #ffffff"
properties

. box square id=#p5x29 at=5.9,86.9,8.1,4.5 style="--skin-box-bg: #ffffff"
events

. box square id=#p5x30 at=5.9,92.2,8.1,4.6 style="--skin-box-bg: #ffffff"
constants

. box square id=#p5x31 at=19.2,75.5,8.1,4.5 style="--skin-box-bg: #ffffff"
methods

. box square id=#p5x32 at=19.2,80.8,8.1,4.5 style="--skin-box-bg: #ffffff"
properties

. box square id=#p5x33 at=19.2,86.9,8.1,4.5 style="--skin-box-bg: #ffffff"
events

. box square id=#p5x34 at=19.2,92.2,8.1,4.6 style="--skin-box-bg: #ffffff"
constants

. box square id=#p5x35 at=32.5,75.5,8.1,4.5 style="--skin-box-bg: #ffffff"
methods

. box square id=#p5x36 at=32.5,80.8,8.1,4.5 style="--skin-box-bg: #ffffff"
properties

. box square id=#p5x37 at=32.5,86.9,8.1,4.5 style="--skin-box-bg: #ffffff"
events

. box square id=#p5x38 at=32.5,92.2,8.1,4.6 style="--skin-box-bg: #ffffff"
constants

. box square id=#p5x39 at=46.1,75.5,8.1,4.5 style="--skin-box-bg: #ffffff"
methods

. box square id=#p5x40 at=46.1,80.8,8.1,4.5 style="--skin-box-bg: #ffffff"
properties

. box square id=#p5x41 at=46.1,86.9,8.1,4.5 style="--skin-box-bg: #ffffff"
events

. box square id=#p5x42 at=46.1,92.2,8.1,4.6 style="--skin-box-bg: #ffffff"
constants

. box square id=#p5x43 at=59.1,75.5,8.1,4.5 style="--skin-box-bg: #ffffff"
methods

. box square id=#p5x44 at=59.1,80.8,8.1,4.5 style="--skin-box-bg: #ffffff"
properties

. box square id=#p5x45 at=59.1,86.9,8.1,4.5 style="--skin-box-bg: #ffffff"
events

. box square id=#p5x46 at=59.1,92.2,8.1,4.6 style="--skin-box-bg: #ffffff"
constants

. box square id=#p5x47 at=73,75.5,8.1,4.5 style="--skin-box-bg: #ffffff"
methods

. box square id=#p5x48 at=73,80.8,8.1,4.5 style="--skin-box-bg: #ffffff"
properties

. box square id=#p5x49 at=73,86.9,8.1,4.5 style="--skin-box-bg: #ffffff"
events

. box square id=#p5x50 at=73,92.2,8.1,4.6 style="--skin-box-bg: #ffffff"
constants

. box square id=#p5x51 at=86,75.5,8.1,4.5 style="--skin-box-bg: #ffffff"
methods

. box square id=#p5x52 at=86,80.8,8.1,4.5 style="--skin-box-bg: #ffffff"
properties

. box square id=#p5x53 at=86,86.9,8.1,4.5 style="--skin-box-bg: #ffffff"
events

. box square id=#p5x54 at=86,92.2,8.1,4.6 style="--skin-box-bg: #ffffff"
constants

. arrow #p5x18 <-> #p5x20 route=elbow

. arrow #p5x18 <-> #p5x21 route=elbow

. arrow #p5x18 <-> #p5x22 route=elbow

. arrow #p5x18 <-> #p5x23 route=elbow

. arrow #p5x18 <-> #p5x24 route=elbow

. arrow #p5x18 <-> #p5x25 route=elbow

. arrow #p5x18 <-> #p5x26 route=elbow

. click box id=#p5x16 at=35.7,21.1,28.2,8.8 style="--skin-box-bg: #ffffff; font-size: 1.51em"
OLE-Interface

. after arrow #p5x16 <-> #p5x18

. after id=#p5x56 at=50,32.4,18.7,7.1 style="font-size: 1.21em"
**initialise & interact**

. click box id=#p5x19 at=35.8,0,28.1,8.8 style="--skin-box-bg: #ffffff; font-size: 1.51em"
ooRexx Application

. after id=#p5x55 at=50,9.6,10.1,7.1 style="font-size: 1.21em"
**messages**

. after arrow #p5x19 <-> #p5x16

. end drawing

. end columns

<!-- slide # 6 (2026-10-15 09:38) ----------------------------- ODP slide # 6 -->
. slide reference="Cf. winextensions.pdf (8. OLE Automation)"
# OLE (ActiveX) Automation

- Information about Windows applications and components is stored in the Windows Registry
    . click
    - **HKEY\_CLASSES\_ROOT**
        . after
        - **CLSID**
            - GUID resp. UUID
            - Global resp. Universal Unique Identifier
        . click
        - **ProgID**
            - A string that can be easily comprehended and memorized by humans
. click
- Identifying (addressing) of a COM Windows component can be done via a *CLSID*, a *ProgID* or a "*moniker*" (a human readable string)

<!-- slide # 7 (2026-10-15 09:38) ----------------------------- ODP slide # 7 -->
. slide reference="Cf. winextensions.pdf (8.3. The OLEObject Class)"
# ooRexx Class ".OLEObject"

. after steps
- Reference documentation available with the Windows version of ooRexx
    . after
    - "ooRexx Windows Reference": **winextensions.pdf**
- "Proxy" class for addressing OLE resp. ActiveX Windows programs, which allows for
    . after
    - Locating and addressing running OLE/ActiveX programs
    - Creating new instances of OLE/ActiveX programs
    - Querying the *published* programming interfaces (methods, properties, constants, events)
    - Sending ooRexx messages to invoke the interfaces
        . after
        - Arguments are automatically converted by ooRexx
        . with
        - Return values are automatically converted by ooRexx

<!-- slide # 8 (2026-10-15 09:38) ----------------------------- ODP slide # 8 -->
. slide reference="Cf. winextensions.pdf (8.3. The OLEObject Class)"
# ooRexx Class ".OLEObject"

. after steps
- Querying/setting of property values using ooRexx messages
    . after
    - Windows properties can be used as if they were ooRexx attributes
- Can intercept events and invoke ooRexx methods by the same name
- Automatically carries out the necessary datatype conversion between Windows and ooRexx, ie. between ooRexx and the following (COM) Windows datatypes
    . after
    - VT\_EMPTY, VT\_NULL, VT\_VOID, VT\_I1, VT\_I2, VT\_I4, VT\_I8, VT\_UI1, VT\_UI2, VT\_UI4, VT\_UI8, VT\_R4, VT\_R8, VT\_CY, VT\_DATE, VT\_BSTR, **VT\_DISPATCH**, *VT\_VARIANT*, **VT\_PTR**, VT\_SAFEARRAY

<!-- slide # 9 (2026-10-15 09:38) ----------------------------- ODP slide # 9 -->
. slide kicker="ooRexx Class “.OLEObject”, 3" reference="Cf. winextensions.pdf (8.3. The OLEObject Class)"
# Methods (1 of 2)

. steps each=after
- **Init( ProgID | CLSID \[, NOEVENTS|WITHEVENTS\] )**
    - Creates a new instance of the OLE/ActiveX program ("COM class")
    - Returns an ooRexx (proxy) object for it
. click
- **GetObject( Moniker \[, SubclassOfOLEObject\] )**
    - Class method, which searches an existing instance of a COM class
    - Returns an ooRexx (proxy) object for it
. click
- **GetConstant( \[someConstantName\] )**
    - Returns the value for the constant named *someConstantName*
    - Returns all published constants with their defined values as a Rexx stem

<!-- slide # 10 (2026-10-15 09:38) --------------------------- ODP slide # 10 -->
. slide kicker="ooRexx Class “.OLEObject”, 4" reference="Cf. winextensions.pdf (8.3. The OLEObject Class)"
# Methods (2 of 2)

. steps each=after
- **GetKnownEvents, GetKnownMethods**
    - Returns a stem containing all published events and methods of the COM class
. click
- **GetOutParameters**
    - Returns an array object with the "out" parameters of the last invocation
. click
- **Dispatch( MessageName \[, Argument1, Argument2, ...\] )**
    - Invokes a method on the Windows side which has the same name as "MessageName" and forwards any supplied arguments
. click
- **UNKNOWN( MessageName \[, ArrayWithArguments\] )**
    - This method processes all unknown messages and forwards them to the OLE/ActiveX program, which gets represented by the proxy object
    - The reason why one can send ooRexx messages successfully to Windows objects!

<!-- slide # 11 (2026-10-15 09:38) --------------------------- ODP slide # 11 -->
# Hint about the examples

. after
- The Windows version of Open ooRexx (ooRexx) is delivered with numerous OLE/ActiveX examples, which can be found under the ooRexx installation directory:

  . with
  ```
  ?\ooRexx\samples\ole
  ```

. click
- Attention!
    . after
    - The following foils only depict a subset of the supplied examples
    . after
    - Therefore please study and run all examples in all of the samples subdirectories
        . after
        - These examples do not change your computer settings permanently!

<!-- slide # 12 (2026-10-15 09:38) --------------------------- ODP slide # 12 -->
. slide reference="Windows 10 Version 21H1, Internet Explorer 11"
# "InternetExplorer.Application" # 1

- ...\\ole\\apps\\MSInternetExplorer\_navigate.rex (executed on 2022-05-03)

. columns 8 4

. after
. after mark 4-6 pen=1
. click mark 9-12 pen=2
. with mark 15 pen=2
. click mark 18-20 pen=3
```rexx
/* create an object for IE */
myIE = .OLEObject~New("InternetExplorer.Application")

myIE~Width  = 1000
myIE~Height = 800
Say "current dimensions of IE are:" myIE~Width "by" myIE~Height

/* set new dimensions and browse IBM homepage */
myIE~Width  = 1280
myIE~Height = 1024
myIE~Visible = .True    -- now show the window
myIE~Navigate("https://www.ibm.com")

/* wait for 10 seconds */
Call SysSleep 10

/* wait for 10 seconds */
myIE~Navigate("https://www.rexxla.org/events")
Call SysSleep 10
myIE~quit
```

. after label
Output:

. with
```output
current dimensions of IE are: 1000 by 800
```


. next column

. after group anim=fade
. layers style=width:100%
. picture img/110_AutoWin_V21/p12-layer-10.png id=#p12L1

. after picture img/110_AutoWin_V21/p12-layer-11.png id=#p12L2 anim=fade

. end layers

. end group

. end columns

<!-- slide # 13 (2026-10-15 09:38) --------------------------- ODP slide # 13 -->
. slide reference="Windows 10 Version 21H1"
# "WScript.Shell"

- ...\\ole\\apps\\WScriptShell.rex

. columns 9 3

. after
. after mark 3-11 pen=1
. click mark 13-17 pen=2
. click mark 19 pen=3
```rexx
WshShellObj = .OLEObject~New("WScript.Shell")

WshEnv = WshShellObj~Environment
Say "Operating system:" WshEnv["OS"]
Say "You have" WshEnv["NUMBER_OF_PROCESSORS"] "processor(s) of",
    WshEnv["PROCESSOR_ARCHITECTURE"] "architecture in your system."

Say "The following directories represent special folders on your system:"
Do Folder Over WshShellObj~SpecialFolders
  Say "   " Folder
End

Say "Creating a shortcut for NOTEPAD.EXE on your Desktop..."
Desktop = WshShellObj~SpecialFolders("Desktop")
ShortCut = WshShellObj~CreateShortcut(Desktop || "\Shortcut to Notepad.lnk")
ShortCut~TargetPath = "%WINDIR%\notepad.exe"
ShortCut~Save

WshShellObj~Popup("Processing of REXX script has finished!")
```


. next column

<div style="height:43px"></div>

. after group anim=fade
. picture img/110_AutoWin_V21/p13-12.png width=22%

. end group

. after group anim=fade
. picture img/110_AutoWin_V21/p13-13.png width=99%

. end group

. end columns

. click label
Possible Output:

. after
```output
Operating system: Windows_NT
You have 2 processor(s) of AMD64 architecture in your system.
The following directories represent special folders on your system:
    C:\Users\Public\Desktop
    C:\ProgramData\Microsoft\Windows\Start Menu
    … cut …

Creating a shortcut for NOTEPAD.EXE on your Desktop...
```

<!-- slide # 14 (2026-10-15 09:38) --------------------------- ODP slide # 14 -->
. slide reference="Windows 10 Version 21H1"
# "WScript.Network"

- ...\\ole\\apps\\WScriptNetwork.rex

. columns 9 3

. after id=#p14x57

```rexx
WshNetObj = .OLEObject~New("WScript.Network")

Say "Computer Name:" WshNetObj~ComputerName
Say "User Domain:" WshNetObj~UserDomain
Say "User Name:" WshNetObj~UserName

Say "The following network drives are currently mapped:"
MappedDrives = WshNetObj~EnumNetworkDrives
Do i=0 To MappedDrives~Count/2 - 1
  Say "   Drive" MappedDrives[i*2] "is mapped to" MappedDrives[i*2 + 1]
End

Say "The following network printers are currently connected:"
Printers = WshNetObj~EnumPrinterConnections
Do i=0 To Printers~Count/2 - 1
  Say "   Port" Printers[i*2] "is connected to" Printers[i*2 + 1]
End
```



. next column

. after id=#p14x58

```output
MappedDrives:
[0] Z:
[1] \\192.168.122.1\Shared

Printers:
[0] PORTPROMPT:
[1] Microsoft Print to PDF
[2] SHRFAX:
[3] Fax
```


. end columns

. click label
Possible Output:

. with id=#p14x59

```output
Computer Name: DESKTOP-NS1T6G3
User Domain: DESKTOP-NS1T6G3
User Name: waldi
The following network drives are currently mapped:
   Drive Z: is mapped to \\192.168.122.1\Shared
The following network printers are currently connected:
   Port PORTPROMPT: is connected to Microsoft Print to PDF
   Port SHRFAX: is connected to Fax
```


. after arrow #p14x57:8 -> #p14x58:1

. with arrow #p14x57:14 -> #p14x58:6

. after arrow #p14x58:9 <-> #p14x59:5

<!-- slide # 15 (2026-10-15 09:38) --------------------------- ODP slide # 15 -->
. slide reference="Windows 10 Version 21H1, Excel 2016 Version 2203"
# "Excel.Application" # 1 (1 of 3)

- ...\\ole\\apps\\MSExcel.rex

  . after
  . click mark 10-28 pen=1
  . click mark 13-18 pen=2
  . click mark 19-23 pen=3
  . click mark 24-27 pen=4
  ```rexx
  excelApplication = .OLEObject~new("Excel.Application")
  excelApplication~visible = .true             -- make Excel visible
  Worksheet = excelApplication~Workbooks~Add~Worksheets[1]
  colTitles = "ABCDEFGHI"                      -- define first nine column letters
  lastLine = 12                                -- number of lines to process
  sumFormula = "=sum(?2:?"lastLine-1")"        -- English formula: question marks will be changed to column letter
  say "sumFormula:      " sumFormula "(question marks will be changed to column letter)"
  xlHAlignRight = excelApplication~getConstant("xlHAlignRight") -- get value of "horizontal align right" constant
  do line = 1 to lastLine                      -- iterate over lines
    do col = 1 to colTitles~length             -- iterate over columns
      colLetter = colTitles[col]               -- get column letter
      cell = Worksheet~Range(colLetter||line)  -- e.g. ~Range("A1")
      if line = 1 then do                -- first row? yes, build title
        cell~value = "Type" colLetter          -- header in first row
        cell~font~bold = .true                 -- make font bold
        cell~Interior~ColorIndex = 36          -- light yellow
        cell~style~horizontalAlignment = xlHAlignRight  -- right adjust title
      end
      else if line = lastLine then do    -- last row? yes, build sums
        /* set formula, e.g. "=sum(B2:B9)" */
        cell~formula = sumFormula~changeStr("?",colLetter) -- adjust formula to column to sum up
        cell~Interior~ColorIndex = 8           -- light blue
      end
      else do -- a row between 2 and 9: fill with random values
        cell~value = random(999999) / 100      -- create a random decimal value
        cell~font~ColorIndex = 11              -- set from black to violet
      end
    end
  end
  ```

<!-- slide # 16 (2026-10-15 09:38) --------------------------- ODP slide # 16 -->
. slide reference="Windows 10 Version 21H1, Excel 2016 Version 2203"
# "Excel.Application" # 1 (2 of 3)

- ...\\ole\\apps\\MSExcel.rex (continued)

  . after
  . after mark 1-13 pen=1
  . click mark 14-19 pen=2
  . click mark 20-26 pen=3
  . click mark 27-28 pen=4
  ```rexx
  sumCell = WorkSheet~range("A"lastLine)       -- get sum-cell of column A
  if sumCell~text = "#NAME?" then
  do
     say
     say "** Excel reports a '#NAME?' error for the 'sum' function! Probable cause: **"
     say "** your local Excel user interface language is not set to English, therefore you need **"
     say "** to adjust the function name 'sum' in the variable 'sumFormula' to your user interface **"
     say "** language and rerun this program (e.g. in German you need to rename 'sum' to 'summe') **"
     say "** sumCell~formula:" sumCell~formula
     say "** sumCell~text:   " sumCell~text
     say "** sumCell~value:  " sumCell~value
     say
  end
  formatString = "#"excelApplication~thousandsSeparator"##0"excelApplication~decimalSeparator"00"
  say "formatString:    " formatString           -- show format string
  excelApplication~useSystemSeparators = .false   -- allow our format string to be used everywhere
  stringRange="A2:"colTitles~right(1)lastLine
  say "formatting range:" stringRange
  WorkSheet~range(stringRange)~numberFormat = formatString -- get range and set its number format
  excelApplication~DisplayAlerts = .false      -- no alerts from now on
  homeDir = value("USERPROFILE",,"ENVIRONMENT")-- get value for environment variable "USERPROFILE"
  fileName = homeDir"\samp09_ooRexx.xlsx"      -- build fully qualified filename
  say "fully qualified fileName:" fileName     -- show fully qualifed filename
  Worksheet~SaveAs(fileName)                   -- save file
     -- let the user inspect the Excel file
  say "Excel sheet got saved to file, press enter to continue ..."
  parse pull .                                 -- wait for user to press enter
  excelApplication~Quit                        -- close Excel
  ```

<!-- slide # 17 (2026-10-15 09:38) --------------------------- ODP slide # 17 -->
. slide reference="Windows 10 Version 21H1, Excel 2016 Version 2203"
# "Excel.Application" # 1 (3 of 3)

. after picture img/110_AutoWin_V21/p17-14.png width=56%

. with label
Possible Output:

. after
```output
sumFormula:       =sum(?2:?11) (question marks will be changed to column letter)
formatString:     #.##0,00
formatting range: A2:I12
fully qualified fileName: C:\Users\waldi\samp09_ooRexx.xlsx
Excel sheet got saved to file, press enter to continue …
```

<!-- slide # 18 (2026-10-15 09:38) --------------------------- ODP slide # 18 -->
. slide reference="Windows 10 Version 21H1"
# "Scripting.FileSystemObject"

- ...\\ole\\apps\\ScriptingFileSystemObject.rex

  . after
  ```rexx
  fsObject = .OLEObject~new("Scripting.FileSystemObject")
  allDrives = fsObject~drives
  if allDrives = .NIL then do
    say "The object did not return information on your drives!"
    exit 1
  end

  do i over allDrives
    info = i~DriveLetter "-"
       /* show the DriveType in human-readable form */
    j = i~DriveType
    select
      when j=1 then info = info "Removable"
      when j=2 then info = info "Fixed"
      when j=3 then info = info "Network"
      when j=4 then info = info "CD-ROM"
      when j=5 then info = info "RAM Disk"
      otherwise info = info "Unknown"
    end

       /* append the ShareName for a network drive... */
    if j=3 then info = info i~ShareName
       /* ...and the VolumeName for the other ones */
    else if i~IsReady then info = info i~VolumeName
    say info
  end
  ```

  . click label
  Possible Output:

  . after
  ```output
  C - Fixed
  Z - Network \\192.168.122.1\Shared
  ```

<!-- slide # 19 (2026-10-15 09:38) --------------------------- ODP slide # 19 -->
. slide reference="Windows 10 Version 21H1, Excel 2016 Version 2203" presenter="Till Winkler"
# "Excel.Application" # 2

...\\ole\\apps\\MSExcel\_cURL.rex

. columns 8 4

. after
. after mark 1-11 pen=1
. click mark 14-30 pen=2
. click mark 32-34 pen=3
```rexx
-- Get information using curl, save results in array
cityArr = .array~of("Vienna", "Graz", "Linz", "Salzburg", "Innsbruck", "Klagenfurt", "Bregenz", -
                    "Eisenstadt", "Sankt-Poelten", "Wien")
cityWeather = .array~new                                    -- array for weather information
do counter i name over cityArr
    command='curl https://wttr.in/'name'?format="%l:+%t"'   -- https://github.com/chubin/wttr.in
    say "#" i":" command                                    -- give user feedback
    outArr = .array~new                                     -- array for stdout
    ADDRESS SYSTEM command with output using (outArr) error using (.array~new)
    cityWeather~append(outArr[1])                           -- append output to array
end

-- Start Excel with empty worksheet
excelApplication = .OLEObject~new("Excel.Application")
excelApplication~visible = .true                            -- make Excel visible
Worksheet = excelApplication~Workbooks~Add~Worksheets[1]    -- add worksheet
-- Create bold collum header in first row
colhead = .array~of("City", "Celsius")                      -- array with header text
do counter col name over colhead
    Worksheet~cells(1,col)~Value = name                     -- insert items from array
    Worksheet~cells(1,col)~font~bold = .true                -- make bold
end
-- Insert information from gained with curl
do counter row name over cityWeather
    parse var name city ":" temperature "°C"               -- parse curled informations
    Worksheet~cells(row+1,1)~Value = city                   -- city in collum 1
    Worksheet~cells(row+1,2)~Value = temperature            -- temperature in collum 2
end
-- Select range for chart, use left upper and lower right cell location
Worksheet~Range("A1:B" || (row+1))~Select                   -- include title row
-- Add Chart
excelApplication~Charts~Add                                 -- create new chart
excelApplication~ActiveChart~HasTitle = .True               -- add title
excelApplication~ActiveChart~ChartTitle~Characters~Text = "Temperature"
```


. next column

. after group anim=fade
. layers style=width:100%
. picture img/110_AutoWin_V21/p19-layer-17.png id=#p19L3

. after picture img/110_AutoWin_V21/p19-layer-18.png id=#p19L4 anim=fade

. end layers

. end group

. after label
Output:

. after
```output
# 1: curl https://wttr.in/Vienna?format="%l:+%t"
# 2: curl https://wttr.in/Graz?format="%l:+%t"
# 3: curl https://wttr.in/Linz?format="%l:+%t"
# 4: curl https://wttr.in/Salzburg?format="%l:+%t"
# 5: curl https://wttr.in/Innsbruck?format="%l:+%t"
# 6: curl https://wttr.in/Klagenfurt?format="%l:+%t"
… cut …
```

. end columns

<!-- slide # 20 (2026-10-15 09:38) --------------------------- ODP slide # 20 -->
. slide reference="Windows 10 Version 21H1, Internet Explorer 11"
# "InternetExplorer.Application" # 2

- ...\\ole\\apps\\ MSInternetExplorer\_events.rex

. columns 9 3

. after
. after mark 2-4 pen=1
. after mark 13-23 pen=2
. after mark 20-21 pen=2
```rexx
/* instantiate an instance of the Internet Explorer */
myIE = .watchedIE~new("InternetExplorer.Application","WITHEVENTS")
myIE~visible = .true
myIE~navigate("http://www.ibm.com/")

/* wait for the OnQuit event of the browser to change */
/* the !active attribute of the REXX object to false  */
myIE~!active = .true
do while myIE~!active = .true
  call syssleep(2)
end

::CLASS watchedIE SUBCLASS OLEObject
/* ... Cut ... Lines deleted, please lookup the original file in your installation ! */
/* this is an event of the Internet Explorer */
::METHOD TitleChange
  use arg Text
  say "The title has changed to:" text
/* this is an event of the Internet Explorer */
::METHOD OnQuit
  self~!active = .false    -- terminates the waiting loop in main code

::METHOD !active ATTRIBUTE -- store the active attribute
```


. next column

. click group anim=fade
. picture img/110_AutoWin_V21/p20-diagram-19.png width=100%

. end group

. end columns

. after label
Output:

. after
```output
The title has changed to: IBM - Österreich | IBM
```

<!-- slide # 21 (2026-10-15 09:38) --------------------------- ODP slide # 21 -->
. slide reference="Press any key to change style!" presenter="Till Winkler"
# "Word.Application"

...\\ole\\apps\\MSWord\_useStyles.rex

. columns 7 2 3

. after
. after mark 1-21 pen=1
. after mark 24-25 pen=1
. click mark 28-47 pen=2
```rexx
Word = .OLEObject~New("Word.Application")
Word~Visible = .TRUE                      -- make Word visible
Document = Word~Documents~Add             -- add document
Selection = Word~Selection                -- selection object
say "# 1: Create: title style..."
Selection~Style = "Title"                 -- Create selection with style: Title
Selection~TypeText("TITLE")               -- give selection a text
Selection~TypeParagraph                   -- add paragraph (go to next line)
say "# 2: Create: heading 1 style..."
Selection~Style = "Heading 1"             -- Create selection with style: Heading 1
Selection~TypeText("HEADING 1")           -- give selection a text
Selection~TypeParagraph                   -- add paragraph (go to next line)
say "# 3: Create: heading 2 style..."
Selection~Style = "Heading 2"             -- Create selection with style: Heading 2
Selection~TypeText("HEADING 2")           -- give selection a text
Selection~TypeParagraph                   -- add paragraph (go to next line)
   -- Note: Usually the style "Normal" follows heading styles
Selection~TypeText("Reset to normal ...") -- "Normal" follows "Heading 2"
Selection~TypeParagraph                   -- add paragraph (go to next line)
say "# 4: Create: normal text style..."
Selection~Style = "Normal"                -- Create selection with style: normal
Selection~TypeText("I am Normal Text.")   -- give selection a text

say "Press any key to change style!"
parse pull                              -- wait for key press

-- Go through each sentence
SentenceCount = Document~Sentences~Count        -- get sentence count
Font = Selection~Font                           -- get font object
do SentenceNumber = 1 to SentenceCount          -- go through each sentence

   Document~Sentences(SentenceNumber)~Select   -- select sentence
   StyleName = Selection~Style~NameLocal       -- get style name of sentence
   Select case StyleName                       -- make changes depending on style name
      when "Title" then do
          Font~Name="Arial"
          Font~Size="38"
          Font~Bold = .TRUE
      end
      when "Heading 1" then do
          Font~Name="Times New Roman"
          Font~Size="22"
          Font~Italic = .TRUE
      end
      otherwise NOP
   end
end
```


. next column

. after picture img/110_AutoWin_V21/p21-20.png width=100%


. next column

<div style="height:93px"></div>

. after picture img/110_AutoWin_V21/p21-21.png width=95%

. end columns

. after label
Output:

<!-- slide # 22 (2026-10-15 09:38) --------------------------- ODP slide # 22 -->
. slide reference="Windows 10 Version 21H1, PowerPoint 2016 Version 2203" presenter="Till Winkler"
# "PowerPoint.Application"

...\\ole\\apps\\MSPowerPoint\_layouts.rex

. columns 8 4

. after
. after mark 1-8 pen=1
. click mark 10-14 pen=2
. click mark 16-25 pen=3
. click mark 27-31 pen=4
```rexx
-- Start PowerPoint
PP = .OLEObject~New("PowerPoint.Application")
PP~Visible = .TRUE                                                    -- make PP visible
-- Add first Slide
PP~Presentations~Add~Slides~Add(1, 1)                                 -- add: Title-Slide
FirstSlide = PP~ActivePresentation~Slides(1)
FirstSlide~Shapes(1)~TextFrame~TextRange = "New Presentation Title"   -- first Shape is Title
FirstSlide~Shapes(2)~TextFrame~TextRange =  "Subtitle at:" .dateTime~new -- second Shape is SubTitle
-- Append several Slides
ActivePresentation = PP~ActivePresentation
ActivePresentation~Slides~Add(2, 1)                                   -- append: second Title-Slide
ActivePresentation~Slides~Add(3, 2)                                   -- append: Title and Text Slide
ActivePresentation~Slides~Add(4, 3)                                   -- append: Title and 2-Columne Text
ActivePresentation~Slides~Add(5, 5)                                   -- append: Title, Text and Chart
-- Check what on Slide
SlideCount = ActivePresentation~Slides~Range~Count                    -- get amount of Slides
say "Your Presentation has" SlideCount "Slides"
do SlideNumber = 1 to SlideCount                                      -- go through each Slide
    ShapeCount = ActivePresentation~Slides(SlideNumber)~Shapes~Range~Count  -- get amount of Shapes per Slide
    say " Slide" SlideNumber "has" ShapeCount "Shapes"
    do ShapeNumber = 1 to ShapeCount                                        -- go through each Shape
        -- List names of Shapes on Slide
        say "  ("ShapeNumber"/"ShapeCount"):" ActivePresentation~Slides(SlideNumber)~Shapes(ShapeNumber)~name
    end
end
-- Save and Close
parse source . . s         -- get fully qualified path of this script
-- create unique file name
fileOut = filespec('location',s)"\PP_" || date("S") || "_" || changeStr(":",time(),"") || ".pptx"
ActivePresentation~SaveAs(fileOut)                                    -- save pptx file
PP~Quit                                                               -- close PP
```


. next column

. after group anim=fade
. layers style=width:88%
. picture img/110_AutoWin_V21/p22-layer-24.png id=#p22L5

. after picture img/110_AutoWin_V21/p22-layer-25.png id=#p22L6 anim=fade

. end layers

. end group

. after label
Possible Output:

. after
```output
Your Presentation has 5 Slides
 Slide 1 has 2 Shapes
  (1/2): Title 1
  (2/2): Subtitle 2
 Slide 2 has 2 Shapes
  (1/2): Title 1
  (2/2): Subtitle 2
 Slide 3 has 2 Shapes
  (1/2): Title 1
  … cut …
```

. end columns

<!-- slide # 23 (2026-10-15 09:38) --------------------------- ODP slide # 23 -->
. slide reference="Windows 10 Version 21H1, Outlook 2016 Version 2203" presenter="Till Winkler"
# "Outlook.Application"

...\\ole\\apps\\MSOutlook.rex

. columns 8 4

. after
. after mark 1-2 pen=1
. click mark 8 pen=2
. click mark 11 pen=3
. click mark 12-21 pen=4
```rexx
Outlook = .OLEObject~new("Outlook.Application")
NameSpace = Outlook~GetNamespace("MAPI")

-- In Outlook folder have numbers:
-- DeletedItems (3), Outbox (4), SentMail (5), Inbox (6),
-- Calendar (9), Contacts (10), Journal (11), Notes (12),
-- Tasks (13)
Inbox = NameSpace~GetDefaultFolder("6")      -- selects Inbox

-- makes Outlook visible and shows Inbox
Inbox~Display

InboxItems = Inbox~Items
MailCount = InboxItems~Count                  -- count items in Inbox
say "You have" MailCount "Mail(s) in your Inbox:"

Do ItemNumber = 1 to MailCount                -- go through each item
  Item = Inbox~Items(ItemNumber)
  Sender = Item~Sender~Name                   -- sender of item
  say "#" ItemNumber"." Sender
end
```


. next column

<div style="height:87px"></div>

. after group anim=fade
. picture img/110_AutoWin_V21/p23-26.png width=100%

. end group

. end columns

. after label
Possible Output:

. after
```output
You have 8 Mail(s) in your Inbox:
# 1. Rony Flatscher
# 2. Rony Flatscher
# 3. Till Winkler
# 4. Rony Flatscher
… cut …
```

<!-- slide # 24 (2026-10-15 09:38) --------------------------- ODP slide # 24 -->
. slide reference="Windows 10 Version 21H1"
# "Access.Application"

- ...\\ole\\apps\\MSAccess\_contacts.rex

  . after
  ```rexx
  -- Initialize string to database path.
  strDB = "c:\temp\newdb.mdb"
  -- Create new instance of Microsoft Access.
  appAccess = .OLEObject~new("Access.Application")
  -- Open database in Microsoft Access window.
  appAccess~NewCurrentDatabase("strDB")
  -- Get Database object variable.
  dbs = appAccess~CurrentDb
  -- Create new table.
  tdf = dbs~CreateTableDef("Contacts")
  -- Create field in new table.
  /* Please note how to access the constant.
     Microsoft documentation and the MS OLEViewer output
     these constants as dbText, dbBinary, etc. - the type library
     however prints them as DB_TEXT, DB_BINARY, etc.. Unless
     documentation is found why the names should be translated,
     the OLE code will *NOT* convert the names. */
  fld = tdf~CreateField("CompanyName", appAccess~getConstant("db_Text"), 40)
  -- Append Field and TableDef objects.
  tdf~Fields~Append(fld)
  dbs~TableDefs~Append(tdf)

  appAccess~quit
  ```

<!-- slide # 25 (2026-10-15 09:38) --------------------------- ODP slide # 25 -->
# ADS – Active Directory Services

. after
- Authentication and authorization of users and computers
    . after
    - Users
    . with
    - Access rights
    . with
    - ...
. after
- Wikipedia overview article
    - https://en.wikipedia.org/wiki/Active\_Directory

<!-- slide # 26 (2026-10-15 09:38) --------------------------- ODP slide # 26 -->
. slide reference="Windows 10 Version 21H1"
# Collecting Information Using ADS, 1
# Retrieve information about a computer with ADSI.

- ...\\ole\\adsi\\adsi1.rex

  . after
  ```rexx
  ComputerName = value("COMPUTERNAME",,"ENVIRONMENT")
  myComputer = .OLEObject~GetObject("WinNT://"||ComputerName||",computer")

  say "Standard properties of this computer:"
  say left("Name:"   ,10) myComputer~name
  say left("Class:"  ,10) myComputer~class
  say left("GUID:"   ,10) myComputer~guid
  say left("ADsPath:",10) myComputer~adspath
  say left("Parent:" ,10) myComputer~parent
  say left("Schema:" ,10) myComputer~schema
  ```

  . click label
  Possible Output:

  . after
  ```output
  Standard properties of this computer:
  Name:      DESKTOP-NS1T6G3
  Class:     Computer
  GUID:      {DA438DC0-1E71-11CF-B1F3-02608C9E7553}
  ADsPath:   WinNT://WORKGROUP/DESKTOP-NS1T6G3
  Parent:    WinNT://WORKGROUP
  Schema:    WinNT://WORKGROUP/Schema/Computer
  ```

<!-- slide # 27 (2026-10-15 09:38) --------------------------- ODP slide # 27 -->
. slide reference="Windows 10 Version 21H1"
# Collecting Information Using ADS, 2
# Get a user's full name and change it..

- ...\\ole\\adsi\\adsi2.rex \
  (Note: this program requires administrator permissions)

. click label
Possible Output:

. after
. after mark 4 pen=1
```rexx
ComputerName = value("COMPUTERNAME",,"ENVIRONMENT")     -- get ComputerName
UserID       = value("USERNAME",,"ENVIRONMENT" )        -- get UserName

userObject = .OLEObject~GetObject("WinNT://"||ComputerName||"/"||UserID||",user")

/* using the object property */
say "The full name for" UserID "is" userObject~FullName

/* using the standard get method for ADSI objects */
say "The full name for" UserID "is" userObject~Get("FullName")

say "Would you like to rename the full name (y/n)?"
pull answer

if answer = "Y" then do
  say "New full name:"
  parse pull answer

  /* set the property                                                      */
  /* as an alternative, the property can also be set with the standard put */
  /* method of ADSI objects:                                               */
  /* userObject~Put("FullName",answer)                                     */
  userObject~FullName=answer

  /* because properties are cached to avoid network calls, changing the    */
  /* properties of an object will only affect the cache at first.          */
  /* the object gets updated with the SetInfo method:                      */
  userObject~SetInfo

  say "updated the full name for" UserID
end
```

. after
```output
rexx adsi2.rex
The full name for Administrator is John Doe
The full name for Administrator is John Doe
Would you like to rename the full name (y/n)?
y
New full name:
John Doe's Mother
updated the full name for Administrator
```

<!-- slide # 28 (2026-10-15 09:38) --------------------------- ODP slide # 28 -->
. slide reference="Windows 10 Version 21H1"
# Collecting Information Using ADS, 3
# Displaying namespaces and domains.

- ...\\ole\\adsi\\adsi5.rex

  . after
  . after mark 1 pen=1
  ```rexx
  myADS = .OLEObject~GetObject("ADs:")

  do namespace over myADS
    say "Domains in" namespace~Name
    do domain over namespace
      if domain \= .nil then
        say "   " domain~name
      else
        say domain
    end
  end
  ```

  . click
  Possible Output (on a standalone Windows PC):

  . after
  ```output
  Domains in WinNT:
  Domains in LDAP:
  ```

<!-- slide # 29 (2026-10-15 09:38) --------------------------- ODP slide # 29 -->
. slide reference="Windows 10 Version 21H1"
# Collecting Information Using ADS, 4
# Inspecting properties of an object.

- ...\\ole\\adsi\\adsi6.rex

. columns 7 5

. after
. after mark 3 pen=1
. after mark 4 pen=1
```rexx
ComputerName = value("COMPUTERNAME",,"ENVIRONMENT")

myDomain = .OLEObject~GetObject("WinNT://"||ComputerName)
mySchemaClass = .OLEObject~GetObject(myDomain~schema)

say "Properties for the" myDomain~name "object:"
say

if mySchemaClass~container = 1 then do
  say myDomain~name "may contain the following objects:"
  do i over mySchemaClass~Containment
    say "   " i
  end
end
else
  say myDomain~name "is not a container."

say
say "Mandatory properties:"
do i over mySchemaClass~MandatoryProperties
  say "   " i
end

say
say "Optional properties:"
do i over mySchemaClass~OptionalProperties
  say "   " i
End
```


. next column

. click label
Possible Output:

. after
```output
Properties for the DESKTOP-NS1T6G3 object:

DESKTOP-NS1T6G3 may contain the following
objects:
    User
    Group
    Service
    FileService
    PrintQueue

Mandatory properties:

Optional properties:
    Owner
    Division
    OperatingSystem
    OperatingSystemVersion
    Processor
    ProcessorCount
    Name
```

. end columns

<!-- slide # 30 (2026-10-15 09:38) --------------------------- ODP slide # 30 -->
. slide reference="Windows 10 Version 21H1"
# Collecting Information Using ADS, 5
# Create a group and several users in it.

- ...\\ole\\adsi\\adsi7.rex \
  (Note: this program requires administrator permissions)

. columns 7 5

. after
. after mark 2 pen=1
```rexx
ComputerName = value("COMPUTERNAME",,"ENVIRONMENT")  -- get ComputerName
computer = .OLEObject~GetObject("WinNT://"||ComputerName)
/* create a new group */
newGroup = computer~Create("group", "REXX-TestGroup")
newGroup~Description = "A test group created with REXX"
newGroup~SetInfo

/* make sure the information in the object cache is up-to-date */
newGroup~GetInfo
say "Created new group" newGroup~Name
say "Description:" newGroup~Description; say
say "Creating 15 users in this group:"
say "User01..User15 with passwords demo01..demo15"
/* create several new users */
do i = 1 to 15
  /* create name and other information */
  userName = "User"right(i,2,'0')
  userFullName = "Demo User Number" i
  userDescription = "A demo user that was created with REXX"
  userPassword = "demo"right(i,2,'0')

  newUser = computer~Create("user", userName)
  newUser~FullName = userFullName
  newUser~Description = userDescription
  newUser~SetPassword(userPassword)
  newUser~SetInfo
  newGroup~Add(newUser~ADsPath)
end
```


. next column

. click label
Output:

. after
```output
Created new group REXX-TestGroup
Description: A test group created with REXX

Creating 15 users in this group:
User01..User15 with passwords demo01..demo15
```

. end columns

<!-- slide # 31 (2026-10-15 09:38) --------------------------- ODP slide # 31 -->
. slide reference="Windows 10 Version 21H1"
# Collecting Information Using ADS, 6
# Remove the users and the group that were created in *adsi7.rex*.

- ...\\ole\\adsi\\adsi8.rex \
  (Note: this program requires administrator permissions)

  . after
  . after mark 2 pen=1
  ```rexx
  ComputerName = value("COMPUTERNAME",,"ENVIRONMENT")  -- get ComputerName
  computer = .OLEObject~GetObject("WinNT://"||ComputerName)

  say "Removing the fifteen users..."
  do i = 1 to 15
    computer~Delete("user", "User"||right(i,2,'0'))
  end

  say "Removing the test group..."

  computer~Delete("group", "REXX-TestGroup")

  say "done"
  ```

  . click label
  Possible Output:

  . after
  ```output
  Removing the fifteen users...
  Removing the test group...
  ```

<!-- slide # 32 (2026-10-15 09:38) --------------------------- ODP slide # 32 -->
# WMI – Windows Management Instrumentation

- Allows to get information of instrumented components and notifications
    - Local and remote computers and devices
    - ...
- Wikipedia overview article
    - https://en.wikipedia.org/wiki/Windows\_Management\_Instrumentation

<!-- slide # 33 (2026-10-15 09:38) --------------------------- ODP slide # 33 -->
. slide reference="Windows 10 Version 21H1"
# Collecting Information Using WMI

- ...\\ole\\wmi\\accounts.rex

  . after
  . after mark 1 pen=1
  ```rexx
  WMIObject = .OLEObject~GetObject("WinMgmts:{impersonationLevel=impersonate}")
  userAccounts = WMIObject~InstancesOf("Win32_Account")

  do instance over userAccounts
    say
    say "="~copies(16) instance~name "="~copies(16)
    do i over instance~properties_
      say left(i~name":",20,' ') i~value
    end
  end
  ```

  . click label
  Possible Output:

  . after
  ```output
  ================ Everyone ================
  Caption:             DESKTOP-NS1T6G3\Everyone
  Description:         DESKTOP-NS1T6G3\Everyone
  Domain:              DESKTOP-NS1T6G3
  InstallDate:         The NIL object
  LocalAccount:        1
  Name:                Everyone
  SID:                 S-1-1-0
  SIDType:             5
  Status:              OK

  ================ LOCAL ================
  Caption:             DESKTOP-NS1T6G3\LOCAL
  … cut …
  ```

<!-- slide # 34 (2026-10-15 09:38) --------------------------- ODP slide # 34 -->
. slide reference="Windows 10 Version 21H1"
# Collecting Information Using WMI

- ...\\ole\\wmi\\process.rex

  . after
  ```rexx
  WMIObject = .OLEObject~GetObject("winmgmts:{impersonationLevel=impersonate}")

  say "Here is a list of currently running processes"

  do process over WMIObject~InstancesOf("win32_process")
    say process~processid process~name
  end
  ```

  . click label
  Possible Output:

  . after
  ```output
  Here is a list of currently running processes
  0 System Idle Process
  4 System
  92 Registry
  328 smss.exe
  436 csrss.exe
  508 wininit.exe
  516 csrss.exe
  600 services.exe
  608 winlogon.exe
  620 lsass.exe
  752 svchost.exe
  …cut…
  ```

<!-- slide # 35 (2026-10-15 09:38) --------------------------- ODP slide # 35 -->
# Further Links

. after
- Rexx Language Association (RexxLA)
    - [Numerous additional links]{.fragment .afterPrevious}\
      [\<http://www.RexxLA.org/\> (2025-10-31)]{.fragment .afterPrevious}
. click
- OLE-/Active-X Query Tool written in ooRexx
    - [Included in ooRexx 5.1.0 (Windows, released May 2025), see installation at\
      `oorexx\samples\ole\oleinfo`]{.fragment .afterPrevious}
    - [If running earlier versions of ooRexx (4.2 or 5.0) Windows download **oleinfo**\
      \<https://wi.wu.ac.at/rgf/wu/lehre/autowin/material/resources/oleinfo.zip\> (2025-10-31)]{.fragment .afterPrevious}
. click
- ooRexx related OLE/ActiveX page, a ***must*** to visit!

  [\<https://wi.wu.ac.at/rgf/wu/lehre/autowin/material/resources/pragmaticlee\_clean\_TW.zip\> (2025-10-31)]{.fragment .afterPrevious}

<!-- slide # 36 (2026-10-15 09:38) --------------------------- ODP slide # 36 -->
# Further Links

. after
- Microsoft Office VBA Reference

  [\<https://docs.microsoft.com/en-us/office/vba/api/overview/\> (2025-10-31)]{.fragment .withPrevious}

. click
- A collection of Microsoft of administrative scripts for Windows
    - A self unarchiving Windows help file\
      \<http://download.microsoft.com/download/.NetEnterpriseServer/Utility/1.0/NT5XP/EN-US/netscrpt.exe\> (2025-10-31)
    . after
    - Collection of Visual Basic scripts for administrative tasks
    . after
    - Simply transcribable to ooRexx
        . after
        - *Hint:* simply replace the dot (**.**) in Visual Basic programs with the ooRexx message operator (the tilde: **\~**)

<!-- slide # 37 (2026-10-15 09:38) ----------- business card (not in the ODP) -->
. slide card logo=none
# Contact