Tag: PAM

  • Computational Thinking: Learning to Solve Problems Before Writing Code

    In the past, when I first heard the expression computational thinking, I immediately associated it with programming.

    I imagined algorithms, programming languages, loops, functions, variables, and all the things we normally connect with software development.

    But while studying the subject, I realized something much more interesting:

    Computational thinking is not really about learning how to code. It is about learning how to structure a problem.

    And that distinction matters.

    You can use computational thinking, for example, before writing a PowerShell script, while troubleshooting an application, when designing an automation, when investigating an incident, or even when solving problems completely unrelated to computers.

    The material I was studying describes computational thinking as a tool for understanding how computers work, designing software, and solving problems. It also emphasizes something important: understanding what computers can and cannot do.

    So I started asking myself:

    What does it actually mean to think computationally?

    This article is my attempt to explain what I learned in the same way I would explain it to a colleague sitting next to me.


    First, forget the computer for a moment

    Suppose I tell you:

    “The application is not working.”

    What would you do?

    You could immediately start checking logs, restarting services, testing the network, looking at the database, checking credentials, inspecting certificates, and opening configuration files.

    But there is a problem.

    You still do not really know what problem you are solving.

    “The application is not working” is not a technical diagnosis.

    It is only a description of a symptom.

    Computational thinking encourages us to transform this vague statement into something structured.

    For example:

    Application is not working
    |
    +-- Is the server reachable?
    |
    +-- Is the service running?
    |
    +-- Is authentication working?
    |
    +-- Can the application reach its dependency?
    |
    +-- Is the expected response being returned?

    Suddenly the problem becomes much easier to reason about.

    That is where computational thinking starts.


    A computer is extremely fast, but extremely literal

    One of the ideas that helped me understand this topic is surprisingly simple.

    Computers are incredibly good at executing instructions.

    But they need sufficiently precise instructions.

    The document explains an algorithm as a sequence of logical steps created before those steps are translated into a programming language.

    The example used in the material is calculating BMI.

    The algorithm is basically:

    1. Ask for the person's weight
    2. Ask for the person's height
    3. Calculate BMI
    4. Display the result

    Only after defining that logic do we translate it into Java, Python, PowerShell, or another programming language.

    That gave me a useful mental model:

    The algorithm is the solution. The code is one implementation of that solution.

    This sounds obvious, but it changes how we approach scripting.

    Instead of opening PowerShell and immediately typing:

    foreach (...)
    {
    ...
    }

    we can first ask:

    What exactly am I trying to accomplish?
    What information do I need?
    What decisions must be made?
    What should happen if something fails?
    What should the final result look like?

    Only then do we write the code.


    The four ideas I use to think about a problem

    While learning computational thinking, I found four concepts particularly useful:

    decomposition, pattern recognition, abstraction, and algorithms.

    The document explicitly discusses decomposition and data abstraction as important techniques when computational thinking must scale to increasingly complex problems.

    I like to translate the four ideas into very simple questions:

    Break it. Compare it. Ignore the noise. Define the steps.

    Let me explain.


    1. Decomposition: break the problem into smaller problems

    Imagine that I receive this task:

    “Create an automation that changes a password on a remote system.”

    That sounds simple.

    Until we start looking more closely.

    We can decompose it:

    Password Management
    ├── Receive account information
    ├── Retrieve current credential
    ├── Connect to target system
    ├── Authenticate
    ├── Generate or receive new password
    ├── Change password
    ├── Validate new password
    ├── Handle errors
    └── Log the result

    Now I no longer have one big problem.

    I have several smaller problems.

    And smaller problems are much easier to understand, test, and troubleshoot.

    Interestingly, the document also discusses decomposition in the context of software architecture: architecture defines components and their interactions, and decomposition is described as an indispensable tool for identifying those parts.

    This made something click for me.

    Decomposition exists at many levels.

    We can decompose:

    a problem
    a solution
    a system
    components
    functions
    instructions

    That is essentially how complexity becomes manageable.


    2. Pattern recognition: have I seen this before?

    Suppose a PowerShell script works with most passwords but fails with some of them.

    At first the failures appear random.

    Then I collect examples:

    Password ABC123! → works
    Password Hello2026! → works
    Password My*Password → fails
    Password Test*123 → fails

    Now something interesting appears.

    There is a pattern.

    The failures are associated with *.

    Instead of investigating:

    “Why does PowerShell randomly fail?”

    I can ask a much narrower question:

    “How is the * character being interpreted when this command is constructed?”

    That is a dramatically better problem.

    Pattern recognition is useful because many technical problems are repetitions with small variations.

    For example:

    Server A fails after certificate renewal
    Server B fails after certificate renewal
    Server C fails after certificate renewal

    The shared characteristic deserves attention.

    Or:

    Account A works
    Account B works
    Account C fails
    Difference:
    Account C belongs to another domain

    Again, we have a clue.

    For me, pattern recognition in troubleshooting means continuously asking:

    What is common between the failures, and what is different between successful and unsuccessful cases?


    3. Abstraction: what actually matters?

    This one was initially less intuitive for me.

    I used to think abstraction was mainly an object-oriented programming concept.

    But it is much broader.

    Imagine that I am troubleshooting an authentication problem, and I have this information:

    Server: APP01
    Operating System: Windows Server 2022
    CPU: 23%
    Memory: 51%
    Disk: 62%
    Datacenter: Amsterdam
    Application version: 4.2
    Authentication: Failed
    Account status: Locked
    Wallpaper: Company Standard

    Do I need all this information?

    Probably not.

    For the problem I am currently investigating, I might reduce the model to:

    Authentication request
    Account
    Locked?
    Credentials valid?
    Permissions correct?

    That reduction is abstraction.

    I am deliberately hiding information that is not currently useful.

    The study material explains that software design itself relies on abstractions: architecture, data structures, interfaces, and components are models of the system that exist before they are represented in code.

    That helped me understand abstraction differently.

    Abstraction is not about ignoring reality.

    It is about creating a useful representation of reality for the problem we are trying to solve.


    4. Algorithms: define the procedure

    After decomposing the problem, identifying patterns, and removing irrelevant information, we need a procedure.

    That procedure is our algorithm.

    For example, imagine a script that checks whether a Windows service is running on 100 servers.

    Before writing PowerShell, we can define:

    Get list of servers
    For each server:
    Test connectivity
    If unreachable:
    Record "Unreachable"
    Continue to next server
    Check service
    If service is running:
    Record "OK"
    Otherwise:
    Record "Service stopped"
    Generate report

    That is already most of the intellectual work.

    The PowerShell implementation becomes the translation of that reasoning into syntax.

    The source material makes the same distinction: an algorithm defines the logical steps, and afterward those steps are converted into code that a computer can interpret.


    Let us solve a real troubleshooting problem

    Suppose I receive the following incident:

    “The automation stopped changing passwords.”

    This is exactly the kind of statement that can send us in twenty different directions.

    Instead, I can apply computational thinking.

    Step 1: Decomposition

    First, I divide the operation:

    Input
    Retrieve credentials
    Connect
    Authenticate
    Build command/request
    Execute
    Receive response
    Validate
    Log result

    Step 2: Find the boundary of the failure

    Now I test each stage.

    Input ✓
    Retrieve credentials ✓
    Connection ✓
    Authentication ✓
    Build command ✓
    Execute command ✗

    This is one of my favorite troubleshooting techniques.

    Instead of asking:

    “Why does the automation not work?”

    I can now ask:

    “What is happening between command construction and command execution?”

    My search space has become much smaller.

    Step 3: Look for patterns

    Maybe successful executions use passwords like:

    Secret123!
    Password2026@

    while failures use:

    Secret*123

    Now I have a hypothesis.

    The special character may be changing how the command is interpreted.

    Step 4 — Create a test

    I do not need to investigate the entire production workflow.

    I can build a small test:

    Input password containing *
    Construct command
    Print or inspect command safely
    Execute
    Observe result

    We have now transformed a large operational incident into a controlled experiment.

    This is computational thinking in practice.


    Complexity is the real enemy

    Another part of the document that caught my attention was its discussion about complexity.

    It states that managing complexity is one of the most important technical concerns in software development.

    It also describes three bad situations:

    Complex solution → simple problem
    Simple but incorrect solution → complex problem
    Complex and inappropriate solution → complex problem

    The important observation is that the underlying problem may already be complex, but we can still accidentally add unnecessary complexity through our solution.

    I think this is extremely relevant outside software engineering too.

    Imagine a requirement:

    “Once per day, retrieve a file from a server.”

    We could design:

    Kubernetes
    Microservices
    Message Queue
    Database
    Multiple APIs
    Monitoring stack

    Technically impressive.

    But maybe all that was actually required was:

    Scheduled PowerShell script
    Retrieve file
    Validate
    Log result

    Computational thinking is not about creating the most sophisticated solution.

    It is about creating an appropriate solution.


    This changed how I think about requirements

    There is another interesting connection here.

    Engineering is not simply receiving a request and implementing exactly what was requested.

    The document describes engineers as people who have to consider users, stakeholders, technical possibilities, cost, and sometimes conflicting objectives in order to create a practical solution.

    That is an important lesson.

    Suppose somebody asks:

    “Can you create a script that restarts the service every hour?”

    The easiest response would be:

    “Sure.”

    But computational thinking encourages another question:

    Why does the service need to be restarted every hour?

    Maybe the restart is not the requirement.

    Maybe it is a workaround for:

    memory leak
    network timeout
    authentication expiration
    application bug
    dependency failure

    If we automate the restart without understanding the underlying problem, we may simply automate the symptom.

    This is where technical work starts becoming engineering.


    Thinking in the small and thinking in the large

    Another concept from the study material that I found useful is the distinction between:

    thinking in the small

    and

    thinking in the large.

    For a small script maintained by one person, relatively simple reasoning may be enough.

    But once the solution grows:

    1 developer
    5 developers
    multiple teams
    many components
    thousands of users
    years of maintenance

    the nature of the problem changes.

    The document explains that when software grows in scale and teams become involved, additional practices are required. Computational thinking expands into techniques such as decomposition, abstraction, encapsulation, information hiding, project management, and software lifecycle management.

    This is interesting because it shows that the solution that works for a small problem does not necessarily scale to a large one.

    A 50-line PowerShell script might be perfectly understandable as:

    Input
    Logic
    Output

    A 5,000-line automation may require:

    Configuration
    Authentication module
    Connection module
    Business logic
    API module
    Validation
    Logging
    Error handling

    Same goal.

    Different scale.

    Different way of thinking.


    A simple framework I want to start using

    After studying this topic, I created a small checklist for myself.

    Whenever I face a technical problem, instead of jumping directly into commands or code, I want to write:

    PROBLEM
    What exactly is happening?
    EXPECTED RESULT
    What should happen instead?
    DECOMPOSITION
    What smaller parts make up this problem?
    PATTERNS
    What works?
    What fails?
    What is common between the failures?
    ABSTRACTION
    Which information is relevant right now?
    What can I ignore temporarily?
    ALGORITHM
    What sequence of tests or actions should I perform?
    VALIDATION
    How will I know that the problem is actually solved?

    There is also one question I find particularly powerful:

    At what exact point does the behavior stop matching what I expected?

    For example:

    Input
    Connection ✓
    Authentication ✓
    API Request ✓
    API Response ✓
    Response Parsing ✗

    Now I do not have an “API integration problem.”

    I have a response parsing problem.

    That is a much better problem to have.


    Computational thinking does not mean thinking like a computer

    This may be the biggest misunderstanding I had before studying the subject.

    Computational thinking does not mean turning ourselves into machines.

    Quite the opposite.

    Computers are extremely good at executing instructions.

    Humans have to decide which instructions make sense in the first place.

    The material summarizes this nicely through the relationship between input, processing, and output: information enters the system, is processed according to instructions, and produces a result.

    Our role is to design that reasoning.

    So today I would describe computational thinking like this:

    Computational thinking is the ability to transform an unclear problem into a structured problem that can be understood, tested, and eventually automated.

    Programming can come afterward.

    And sometimes programming is not necessary at all.


    Final thoughts

    The more I study software development and automation, the more I realize that knowing commands is only part of the job.

    Knowing PowerShell syntax is useful.

    Knowing an API is useful.

    Knowing Linux, Windows, networking, or security products is useful.

    But when something unfamiliar breaks, none of us knows every command.

    What remains useful is the ability to reason:

    Understand
    Break down
    Find patterns
    Remove noise
    Create a hypothesis
    Test
    Learn
    Adjust

    That is probably the part of computational thinking that interests me the most.

    It gives us a method for dealing with problems we have never seen before.

    And for someone working with technology, troubleshooting, scripting, and automation, I think that may be much more valuable than simply memorizing another command.


    Reference

    This article (It has been written in Portuguese) was inspired by my study of the “Pensamento Computacional” educational material published by the Universidade Federal de Goiás (UFG). The material approaches computational thinking through computer systems, algorithms, software engineering, complexity, decomposition, abstraction, and software design.

    One detail I would keep exactly as it is in this version is the curious-student voice. Instead of presenting yourself as someone teaching “the correct methodology,” the article repeatedly uses ideas such as “this made something click for me,” “I started asking myself,” and “the way I understand it now.” That makes the article fit very naturally with the conversational technical style you have been developing for your blog.

    https://portaldelivros.ufg.br/index.php/cegrafufg/catalog/view/212/127/595

  • Troubleshooting an IDIRA/CyberArk CPM TPC Plugin: When the Problem Wasn’t the PowerShell Script

    Recently, I ran into an interesting issue with a custom IDIRA/CyberArk CPM TPC plugin that I use to manage passwords in One Identity.

    The plugin had been working before, but suddenly password changes started failing.

    At first, everything pointed toward the PowerShell script. After all, the TPC plugin launches PowerShell, the script makes the API calls to One Identity, and the failure appeared during that process.

    But as is often the case with CPM troubleshooting, the first error was only part of the story.

    In this post, I want to walk through what happened, the two separate problems I found, and why understanding the PluginManagerUser is important when troubleshooting custom CPM plugins.


    How the Plugin Works

    The plugin is a TPC-based CPM plugin.

    At a high level, the workflow looks like this:

    CPM
    |
    +-- Process.ini
    |
    +-- Prompts.ini
    |
    +-- PowerShell script
    |
    +-- Connect to One Identity
    +-- Build the API request
    +-- Change the password
    +-- Return the result to the TPC plugin

    The Process.ini controls the execution flow, while Prompts.ini identifies the responses returned during the process.

    The PowerShell script performs the actual interaction with the One Identity API.

    When everything works, the process is straightforward:

    CPM
    |
    v
    TPC Plugin
    |
    v
    PowerShell
    |
    v
    One Identity API
    |
    v
    Password changed

    Then one day, it stopped working.


    Problem 1: At line:1 char:143

    The first error I saw in the TPC trace was:

    At line:1 char:143

    The plugin attempted to find a known prompt in Prompts.ini, but none of the expected responses matched.

    Eventually, the generic standard prompt matched:

    .*

    At first, this made the problem look like an error inside the PowerShell script.

    I tried adding things such as:

    Write-Host
    Write-Output

    and even:

    Start-Transcript

    But nothing useful appeared in the logs.

    That turned out to be an important clue.

    The problem was happening before the PowerShell script was actually able to execute.


    The Password Was Breaking the PowerShell Command

    After looking more closely at the command generated by the TPC plugin, I found the first problem.

    One of the passwords contained a special character:

    *

    The password was being inserted directly into the command used to launch PowerShell.

    Conceptually, the TPC plugin was doing something similar to:

    PowerShell.exe & ".\bin\MyPlugin.ps1" -uri <address> -serviceAccPass <password> -UpdateUserPass <newpassword>

    Because the password became part of the PowerShell command line, the special character affected how PowerShell parsed the command.

    The location reported by:

    At line:1 char:143

    was exactly where the problematic character appeared in the generated command.

    This also explained why my Write-Host, Write-Output, and Start-Transcript debugging attempts didn’t help.

    The execution was effectively:

    TPC
    |
    v
    Start PowerShell.exe
    |
    v
    PowerShell parses command line
    |
    X Parsing error
    |
    +--> At line:1 char:143

    The script itself never got far enough to execute my debugging commands.

    After correcting the password/argument handling problem, PowerShell could launch the script again.

    But then I was back to the original problem.


    Problem 2: A Strange File Called 1

    Once the PowerShell parsing problem was solved, the plugin still wasn’t successfully managing the password.

    This time I noticed something very unusual.

    Every time the plugin failed, a file called simply:

    1

    with no extension was created under the CPM installation directory:

    [Drive]:\Program Files (x86)\CyberArk\PasswordManager

    That immediately changed the direction of my troubleshooting.

    Why was the plugin creating a file in the root of the Password Manager installation instead of writing where it was supposed to?

    This started looking less like an API problem and more like an execution-context or filesystem-permissions problem.

    And then I performed a test that provided the most useful clue in the entire investigation.


    The Test That Pointed Me Toward Permissions

    As part of the troubleshooting, I temporarily added the following parameter to the CyberArk platform:

    RunPluginWithHighPrivilege=Yes

    Then I ran the password change again.

    This time, the plugin worked successfully.

    That was a major clue.

    The PowerShell script was the same.

    The One Identity API was the same.

    The account being managed was the same.

    The TPC logic was essentially the same.

    But changing the privilege context under which the plugin executed changed the result from:

    FAILURE

    to:

    SUCCESS

    That immediately made me think:

    If the plugin works with higher privileges but fails under its normal execution context, maybe the problem isn’t the plugin logic at all. Maybe the normal plugin user doesn’t have permission to access something it needs.

    That shifted my investigation toward the Windows security context used to execute CPM plugins.

    And that eventually led me to the PluginManagerUser.


    Understanding the PluginManagerUser

    The PluginManagerUser was introduced starting with CPM version 12.2.4.

    It is a local Windows user involved in the CPM plugin execution architecture and is used by the CyberArk Password Manager service when executing plugins.

    This separation is important from a security perspective.

    We generally don’t want every plugin to execute with unnecessarily high privileges. The principle of least privilege applies here as well: a plugin should have only the permissions it requires to operate, rather than being granted elevated access to the server.

    This also means that there can be an important difference between:

    "I can run the PowerShell script successfully."

    and:

    "The CPM can run the PowerShell script successfully."

    Those are not necessarily equivalent tests.

    For example, I might log onto the CPM server and manually execute:

    .\rb_OneIdentity-Change.ps1

    under my administrative account.

    The execution context is then something like:

    Administrator
    |
    v
    PowerShell
    |
    v
    Script

    But during normal CPM operation, the plugin is executed under the security controls and permissions associated with the CPM plugin infrastructure:

    CPM
    |
    v
    Plugin execution
    |
    v
    PluginManagerUser context/permissions
    |
    v
    PowerShell
    |
    v
    Script

    So a script working manually doesn’t necessarily prove that the CPM execution context has everything it needs.


    Why RunPluginWithHighPrivilege=Yes Was Such an Important Test

    This was probably the most useful diagnostic step in my troubleshooting.

    The fact that:

    RunPluginWithHighPrivilege=No

    resulted in failure, while temporarily using:

    RunPluginWithHighPrivilege=Yes

    allowed the plugin to work, strongly pointed toward an execution-context or permissions difference.

    It gave me a much narrower troubleshooting question:

    What can the elevated execution context access that the normal PluginManagerUser context cannot?

    Instead of continuing to modify the API calls or PowerShell logic, I started looking at:

    • filesystem permissions;
    • CPM folders;
    • hardening;
    • permissions assigned to PluginManagerUser;
    • and directories the plugin might need during execution.

    This is also why I would treat RunPluginWithHighPrivilege=Yes primarily as a troubleshooting clue in this scenario, rather than simply leaving it enabled as the fix.

    If elevating the plugin resolves the problem, I want to understand why before deciding that elevation is necessary.


    Finding the Actual Permissions Problem

    Once I started looking at the problem from that perspective, things began to make sense.

    I checked the permissions on the CPM directories and found that the PluginManagerUser did not have all the required permissions on the:

    [Drive]:\Program Files (x86)\CyberArk\PasswordManager\Logs

    directory.

    I also found missing permissions associated with:

    [Drive]:\Program Files (x86)\CyberArk\PasswordManager\Scanner

    This was consistent with what I had observed.

    The plugin worked when I forced it to execute with higher privileges, but failed under its normal security context.

    After correcting the required permissions for PluginManagerUser, I removed the need to rely on the high-privilege test and executed the password management operation again.

    This time:

    CPM
    |
    v
    TPC Plugin
    |
    v
    PowerShell
    |
    v
    One Identity API
    |
    v
    Password successfully managed

    The CPM was able to manage the One Identity password successfully again.


    Hardened CPMs Make the Execution Context Even More Important

    This becomes especially relevant on hardened CPM servers.

    IDIRA/CyberArk has specific guidance around the PluginManagerUser profile and permissions when using the Web Application Framework on a hardened CPM.

    That is an important reminder that hardening is not simply about restricting access.

    The required execution accounts still need access to the specific resources that allow CPM components and plugins to function correctly.

    A useful way of thinking about it is:

    Too many permissions
    |
    v
    Security problem
    Correct permissions
    |
    v
    Plugin works securely
    Too few permissions
    |
    v
    Plugin failure

    The goal isn’t to give PluginManagerUser broad administrative permissions.

    The goal is to make sure that the account has the specific permissions required by the CPM architecture and the plugin execution process.

    That is why the high-privilege test was useful: it demonstrated that permissions were likely involved without making elevated execution the permanent solution.


    Why This Incident Was Misleading

    What made this troubleshooting particularly interesting was that I actually encountered two separate problems.

    The first was:

    At line:1 char:143

    which led me to a special-character issue in the command used to launch PowerShell.

    After resolving that, the original problem remained.

    Then I noticed the unexpected:

    1

    file.

    Finally, the high-privilege test gave me the key clue:

    Normal plugin execution
    |
    X
    FAILURE
    RunPluginWithHighPrivilege=Yes
    |
    v
    SUCCESS

    That led me to investigate PluginManagerUser, where I eventually found the missing permissions.

    My complete troubleshooting path therefore looked something like this:

    Password change fails
    |
    v
    "At line:1 char:143"
    |
    v
    Inspect generated PowerShell command
    |
    v
    Find special-character problem
    |
    v
    Fix PowerShell invocation
    |
    v
    Original failure remains
    |
    v
    Notice unexpected file "1"
    |
    v
    Test RunPluginWithHighPrivilege=Yes
    |
    v
    Plugin works!
    |
    v
    Suspect execution context/permissions
    |
    v
    Investigate PluginManagerUser
    |
    v
    Find missing permissions on Logs / Scanner
    |
    v
    Correct permissions
    |
    v
    Run plugin normally
    |
    v
    SUCCESS

    This is a good reminder that fixing one error doesn’t necessarily mean you have found the original root cause.

    Sometimes you remove one layer of the problem only to expose the next one.


    What I Would Check Next Time

    If I encountered a similar TPC plugin problem again, I would keep a few things in mind.

    1. Look at the exact PowerShell command

    If the trace reports:

    At line:1 char:143

    don’t immediately assume it means line 143 of the .ps1 file.

    Look at the command being passed to PowerShell.exe.

    The error may be occurring while PowerShell is parsing the command that is supposed to launch your script.


    2. Be Careful With Passwords as Command-Line Arguments

    Passwords are unpredictable input.

    A plugin may work perfectly with:

    Password123

    and fail when the next generated password contains a character that interacts badly with the way the command line is constructed.

    A custom CPM plugin should be designed with the complete password policy in mind.


    3. Compare Normal and Elevated Execution

    If appropriate in your troubleshooting environment, comparing normal plugin execution with an elevated execution can provide a valuable diagnostic clue.

    In my case:

    RunPluginWithHighPrivilege=Yes

    wasn’t the final solution.

    It was the test that pointed me toward the solution.


    4. Understand the PluginManagerUser

    When troubleshooting modern CPM plugins, don’t forget about the Windows account under which plugin operations are being performed.

    Check:

    • folder permissions;
    • user profile requirements;
    • hardening;
    • GPOs;
    • local security policies;
    • access to CPM directories;
    • and anything else the plugin needs to read, write, or execute.

    5. Pay Attention After CPM Hardening or Upgrades

    If a plugin worked previously and starts failing after:

    • a CPM upgrade;
    • hardening changes;
    • security baseline changes;
    • GPO deployment;
    • filesystem ACL modifications;

    then permissions should be high on the troubleshooting list.

    This is particularly important with PluginManagerUser, because a script may continue to work perfectly when tested manually by an administrator while failing when executed through the CPM.


    Final Thoughts

    The most useful lesson from this incident wasn’t specifically about One Identity, PowerShell, or even TPC plugins.

    It was about execution context.

    When developing or troubleshooting a CPM plugin, we naturally focus on:

    Process.ini
    +
    Prompts.ini
    +
    PowerShell
    +
    API

    But underneath all of them there is another layer:

    Windows security context
    +
    Filesystem permissions
    +
    CPM hardening

    If that layer isn’t correct, a perfectly valid plugin can still fail.

    In my case, the troubleshooting became much clearer when I temporarily tested:

    RunPluginWithHighPrivilege=Yes

    and the plugin suddenly worked.

    That told me to stop asking:

    “What’s wrong with my PowerShell?”

    and start asking:

    “What can this execution context access that the normal plugin execution context cannot?”

    That question eventually led me to the missing PluginManagerUser permissions on the Logs and Scanner directories.

    After correcting those permissions, the CPM was able to manage the One Identity passwords normally again without relying on the high-privilege workaround.

    So if you ever find yourself thinking:

    “The PowerShell works manually. Why doesn’t it work when CPM runs it?”

    don’t just look at the script.

    Look at who is running it — and what that user is allowed to access.


    References

    IDIRA/CyberArk Community — What are the differences between the PasswordManagerUser and PluginManagerUser?

    Useful background for understanding the different Windows accounts associated with CPM operations and the role of PluginManagerUser.

    IDIRA/CyberArk Community — CPM WebApplication Framework requires PluginManagerUser profile created for hardened CPM

    Useful reference when investigating PluginManagerUser, CPM hardening, user-profile requirements, and the permissions required for plugin execution on a hardened CPM.

  • Understanding IDIRA (CyberArk) TPC Plugins: Process.ini, Prompts.ini, PowerShell, and the State Machine

    When I first started looking at IDIRA (CyberArk) Terminal Plugin Controller (TPC) plugins, I found the concept a little confusing.

    There was a Process.ini, a Prompts.ini, transitions, states, conditions, FAIL(), PowerShell output… and somehow all of these pieces were supposed to work together to perform a password change.

    Once I understood one key concept, however, everything started to make much more sense:

    A TPC plugin is essentially a state machine.

    In this article, I want to explain how I understand TPC plugins now, using a practical scenario: an IDIRA (CyberArk) CPM plugin that launches a PowerShell script to change a user’s password through a REST API.

    The goal is not to build the complete plugin here. Instead, I want to focus on the architecture and, especially, on how these components interact:

    • Process.ini
    • Prompts.ini
    • PowerShell
    • stdout
    • API responses
    • TPC transitions
    • END
    • FAIL()
    • CPM error codes

    The Scenario

    Imagine that we need to manage accounts stored in an application that exposes a REST API.

    Our architecture looks roughly like this:

    CyberArk CPM
    |
    v
    CyberArk TPC
    |
    v
    PowerShell
    |
    v
    REST API

    The PowerShell script performs the actual REST API operations.

    TPC, on the other hand, controls the execution of that script.

    This distinction is important.

    I like to think about the responsibilities this way:

    Process.ini
    "What should I do?"
    Prompts.ini
    "What did I receive?"
    PowerShell
    "Do the actual work."

    Once I started thinking about TPC this way, the relationship between the files became much easier to understand.


    First: What Is a State Machine?

    Before discussing the files, we need to understand the basic idea of a state machine.

    The name sounds more complicated than the concept actually is.

    A state machine basically says:

    “I am currently at this step. If something happens, move to another step.”

    Think about a washing machine:

    OFF
    |
    v
    FILLING WATER
    |
    v
    WASHING
    |
    v
    RINSING
    |
    v
    SPINNING
    |
    v
    FINISHED

    Each step is a state.

    The movement from one state to another is a transition.

    For example:

    Current state:
    WASHING
    Condition:
    Washing time finished
    Next state:
    RINSING

    TPC follows a very similar concept.

    A simplified password-change workflow could look like this:

    Start PowerShell
    |
    v
    Get Current Password
    |
    v
    Get New Password
    |
    v
    Call API
    |
    +----------+
    | |
    Success Error
    | |
    v v
    END FAIL

    This is our state machine.


    The Role of Process.ini

    The Process.ini file defines the workflow of the plugin.

    Two particularly important sections are:

    [states]

    and:

    [transitions]

    A state represents something TPC can execute.

    For example:

    [states]
    StartScript=(spawn)PowerShell.exe -File OneIdentity.ps1
    SendCurrentPassword=<pmpass>
    SendNewPassword=<pmnewpass>
    END=

    The first state starts PowerShell.

    The next states can send information to the running process.

    Eventually, the plugin reaches either a successful state such as END or an error state.


    Starting PowerShell from TPC

    One of the things I initially wanted to understand was:

    Who actually starts the PowerShell script?

    The answer is the Process.ini.

    For example:

    StartScript=(spawn)PowerShell.exe -File OneIdentity.ps1

    The (spawn) built-in action tells TPC to launch the process.

    The architecture therefore becomes:

    CPM
    |
    v
    TPC
    |
    | Process.ini
    |
    | (spawn)
    v
    PowerShell.exe
    |
    v
    OneIdentity.ps1

    This also means that Prompts.ini does not launch PowerShell.

    That file has a completely different job.


    The Role of Prompts.ini

    If Process.ini tells TPC what to do, Prompts.ini helps TPC understand what it received.

    Suppose our PowerShell script writes:

    Write-Output "CA_REQUEST_CURRENT_PASSWORD"

    TPC receives this through the PowerShell output.

    We can define a condition in Prompts.ini:

    [conditions]
    GetCurrentPassword=CA_REQUEST_CURRENT_PASSWORD

    We have effectively told TPC:

    “If you see CA_REQUEST_CURRENT_PASSWORD, consider the condition GetCurrentPassword to have occurred.”

    That condition can now be used by the state machine.


    Understanding TPC Transitions

    This was probably the part that made everything click for me.

    Inside Process.ini, we can have:

    [transitions]
    StartScript,GetCurrentPassword,SendCurrentPassword

    The format is:

    CurrentState,Condition,NextState

    In our example:

    StartScript,GetCurrentPassword,SendCurrentPassword
    | | |
    | | |
    Process.ini Prompts.ini Process.ini

    We can read this almost like an English sentence:

    “While I am in StartScript, if I receive the GetCurrentPassword condition, move to SendCurrentPassword.”

    That is the state machine in action.


    How PowerShell and TPC Communicate

    This is where TPC becomes particularly interesting.

    The PowerShell script and TPC can effectively have a conversation.

    PowerShell can write something to stdout:

    Write-Output "CA_REQUEST_CURRENT_PASSWORD"

    TPC receives it.

    Prompts.ini recognizes it:

    GetCurrentPassword=CA_REQUEST_CURRENT_PASSWORD

    The transition then tells TPC what to do:

    StartScript,GetCurrentPassword,SendCurrentPassword

    And the next state might be:

    SendCurrentPassword=<pmpass>

    The communication looks like this:

    PowerShell
    |
    | stdout
    |
    | CA_REQUEST_CURRENT_PASSWORD
    v
    TPC
    |
    | Prompts.ini matches it
    v
    GetCurrentPassword
    |
    | transition
    v
    SendCurrentPassword
    |
    | <pmpass>
    v
    PowerShell

    PowerShell can read the value using standard input, for example:

    $currentPassword = [Console]::ReadLine()

    The same concept can be used for the new password.


    Why Not Pass Passwords on the PowerShell Command Line?

    It might be tempting to do something like:

    PowerShell.exe script.ps1 username CurrentPassword NewPassword

    But that is not a good design for credentials.

    Instead, TPC can start the PowerShell process and then provide sensitive information through the interaction with the running process.

    For example:

    TPC starts PowerShell
    |
    v
    PowerShell asks for current password
    |
    v
    TPC sends <pmpass>
    |
    v
    PowerShell asks for new password
    |
    v
    TPC sends <pmnewpass>

    The PowerShell script doesn’t need to retrieve those passwords directly from the Vault.

    TPC already has access to the relevant CPM variables.


    Calling the REST API

    Once PowerShell has the information it needs, it can perform the real operation.

    Conceptually:

    try {
    Write-Output "DEBUG: Calling password change API"
    $response = Invoke-RestMethod `
    -Uri $ApiUrl `
    -Method POST `
    -Body $Body
    Write-Output "CA_API_SUCCESS"
    }
    catch {
    Write-Output "CA_API_ERROR"
    }

    But we can make this much better.

    Instead of simply returning a generic error, PowerShell can interpret the API response and translate it into something meaningful to TPC.


    Using Control Messages

    I found it useful to think of PowerShell output as having two categories.

    The first category is control messages.

    For example:

    CA_API_SUCCESS
    CA_AUTHENTICATION_FAILED
    CA_USER_NOT_FOUND
    CA_PASSWORD_POLICY_REJECTED
    CA_SERVER_ERROR
    CA_UNEXPECTED_ERROR

    These messages are designed specifically for TPC.

    They can be mapped in Prompts.ini:

    [conditions]
    Success=CA_API_SUCCESS
    AuthenticationFailed=CA_AUTHENTICATION_FAILED
    UserNotFound=CA_USER_NOT_FOUND
    PasswordPolicyRejected=CA_PASSWORD_POLICY_REJECTED
    ServerError=CA_SERVER_ERROR
    UnexpectedError=CA_UNEXPECTED_ERROR

    The state machine can then react appropriately.


    Don’t Make TPC Understand the Entire API

    One design decision I particularly like is keeping API-specific complexity inside PowerShell.

    Imagine the API returns:

    HTTP 400

    with a JSON response indicating:

    Password does not meet complexity requirements.

    Instead of making Prompts.ini understand the entire JSON response, PowerShell can translate it:

    HTTP 400
    +
    API response
    +
    PowerShell interpretation
    |
    v
    CA_PASSWORD_POLICY_REJECTED

    TPC only needs to understand:

    CA_PASSWORD_POLICY_REJECTED

    This gives us a nice separation:

    REST API
    |
    | complicated HTTP/JSON response
    v
    PowerShell
    |
    | interprets the response
    v
    Simple CONTROL message
    |
    v
    Prompts.ini
    |
    v
    Process.ini

    PowerShell becomes the translation layer between the external API and TPC.


    Not Every PowerShell Output Needs to Be in Prompts.ini

    This was another important realization for me.

    PowerShell can generate output that is useful for troubleshooting without using that output to control the state machine.

    For example:

    Write-Output "DEBUG: Starting password change"
    Write-Output "DEBUG: Target user = $Username"
    Write-Output "DEBUG: Calling REST API"
    Write-Output "DEBUG: HTTP Status = $StatusCode"
    Write-Output "DEBUG: API returned an error"
    Write-Output "CA_USER_NOT_FOUND"

    Only:

    CA_USER_NOT_FOUND

    needs to be defined in Prompts.ini.

    The other messages exist for troubleshooting.

    Conceptually:

    PowerShell stdout
    |
    +--- DEBUG: Calling REST API
    | |
    | +--> useful for humans
    |
    +--- DEBUG: HTTP Status = 404
    | |
    | +--> useful for humans
    |
    +--- CA_USER_NOT_FOUND
    |
    +--> Prompts.ini
    |
    v
    State machine

    This gives us much better visibility when investigating a plugin failure.


    Debug Messages vs Control Messages

    I therefore like to keep a clear distinction.

    Diagnostic messages:

    DEBUG: Authentication request started
    DEBUG: Authentication successful
    DEBUG: Password change request started
    DEBUG: HTTP status = 400
    DEBUG: API error message = Password policy violation

    Control messages:

    CA_API_SUCCESS
    CA_AUTHENTICATION_FAILED
    CA_USER_NOT_FOUND
    CA_PASSWORD_POLICY_REJECTED
    CA_SERVER_ERROR
    CA_UNEXPECTED_ERROR

    Diagnostic messages are primarily for humans.

    Control messages are primarily for the TPC state machine.

    That distinction makes the plugin much easier to troubleshoot and maintain.


    Be Very Careful With Debug Output

    There is an important security consideration here.

    If PowerShell output can end up in TPC/CPM traces, we should never do this:

    Write-Output "Current password = $CurrentPassword"
    Write-Output "New password = $NewPassword"
    Write-Output "Bearer token = $Token"

    The same applies to blindly logging complete REST request bodies:

    Write-Output ($Body | ConvertTo-Json)

    If the body contains the new password, we have just written a credential into a log file.

    Instead, log the operation:

    Write-Output "DEBUG: Current password received from TPC"
    Write-Output "DEBUG: New password received from TPC"
    Write-Output "DEBUG: Authentication token obtained"
    Write-Output "DEBUG: Password change request submitted"
    Write-Output "DEBUG: HTTP Status = $StatusCode"

    Log what happened, not the secrets used to make it happen.


    What Does FAIL() Do?

    Another important part of Process.ini is the FAIL() built-in action.

    For example:

    UserNotFound=FAIL(One Identity user was not found,8102)

    When the state machine reaches UserNotFound, TPC terminates the plugin execution as a failure.

    We can think about the syntax as:

    FAIL(ErrorMessage,ErrorCode)

    For example:

    One Identity user was not found

    is the human-readable error, while:

    8102

    is the plugin return code.

    A transition could look like:

    CallAPI,UserNotFoundCondition,UserNotFound

    The complete flow becomes:

    PowerShell
    |
    | CA_USER_NOT_FOUND
    v
    Prompts.ini
    |
    | UserNotFoundCondition
    v
    Process.ini transition
    |
    v
    UserNotFound
    |
    v
    FAIL(One Identity user was not found,8102)
    |
    v
    TPC terminates with failure
    |
    v
    CPM

    END vs FAIL()

    This also makes the difference between END and FAIL() easy to understand.

    Successful path:

    API
    |
    | success
    v
    PowerShell
    |
    | CA_API_SUCCESS
    v
    Prompts.ini
    |
    | Success
    v
    Process.ini
    |
    v
    END
    |
    v
    SUCCESS

    Failure path:

    API
    |
    | user doesn't exist
    v
    PowerShell
    |
    | CA_USER_NOT_FOUND
    v
    Prompts.ini
    |
    | UserNotFound
    v
    Process.ini
    |
    v
    FAIL(...,8102)
    |
    v
    FAILURE

    In other words, most paths through our state machine will eventually lead to either:

    END

    or:

    FAIL()

    Designing Good Plugin Error Codes

    I also wouldn’t simply make the plugin error code equal to the HTTP status.

    For example:

    HTTP 401 → plugin error 401
    HTTP 404 → plugin error 404

    The HTTP status describes what happened at the REST protocol/API level.

    The plugin return code should describe the problem from the CPM/plugin perspective.

    For example, we might define:

    8101 - Authentication failed
    8102 - Target user not found
    8110 - Authorization denied
    8141 - Password rejected by policy
    8142 - Password change not permitted
    8161 - API timeout
    8162 - Connection failure
    8163 - TLS/certificate problem
    8164 - API server error
    8181 - Invalid API response
    8199 - Unexpected error

    The exact range should be chosen carefully for your environment and checked against CyberArk’s documented/reserved codes, but the important idea is to create a consistent error catalog instead of choosing random numbers.

    The architecture then becomes:

    HTTP/API response
    |
    v
    PowerShell determines the meaning
    |
    v
    CONTROL MESSAGE
    |
    v
    Prompts.ini condition
    |
    v
    Process.ini transition
    |
    v
    Plugin error code

    For example:

    HTTP 400
    +
    "Password does not meet policy"
    |
    v
    CA_PASSWORD_POLICY_REJECTED
    |
    v
    PasswordPolicyRejected
    |
    v
    FAIL(New password rejected by password policy,8141)

    This is much more useful than simply reporting:

    HTTP 400

    to whoever is troubleshooting the CPM.


    Putting Everything Together

    Let’s look at the complete architecture one more time.

                         CyberArk CPM

    |

    v

    CyberArk TPC

    |

    Process.ini

    |

    spawn

    |

    v

    PowerShell

    |

    "CA_REQUEST_PASSWORD"

    |

    stdout

    |

    v

    Prompts.ini

    |

    condition matched

    |

    v

    Process.ini transition

    |

    Send <pmpass>
    >
    |

    stdin

    |

    v

    PowerShell

    |

    v

    REST API

    |

    HTTP + JSON

    |

    v

    PowerShell

    |

    interprets result

    |

    +-------------+-------------+

    | |

    CA_API_SUCCESS CA_USER_NOT_FOUND

    | |

    v v

    Prompts.ini Prompts.ini

    | |

    v v

    Process.ini Process.ini

    | |

    v v

    END FAIL(...,8102)

    | |

    v v

    SUCCESS FAILURE

    Once I understood this flow, TPC stopped looking like a collection of unrelated INI files and started looking like a relatively simple orchestration engine.


    The Mental Model I Use

    If I had to summarize everything in a few lines, this is what I would remember:

    Process.ini
    = What should I DO?
    Prompts.ini
    = What did I RECEIVE?
    PowerShell
    = Do the real work.
    stdout
    = PowerShell talks to TPC.
    stdin
    = TPC talks to PowerShell.
    Conditions
    = Give meaning to received output.
    Transitions
    = Decide where the state machine goes next.
    END
    = Finish successfully.
    FAIL()
    = Finish with an error.

    And perhaps the most useful sentence of all:

    “I am in this state. If I receive this condition, move to that state.”

    That simple sentence explains a surprisingly large part of how a CyberArk TPC plugin works.


    Final Thoughts

    TPC plugins initially looked more complicated to me than they actually were.

    The breakthrough was understanding that Process.ini, Prompts.ini, and PowerShell are not competing with each other. They have different responsibilities.

    Process.ini orchestrates the workflow.

    Prompts.ini recognizes conditions.

    PowerShell handles the application-specific logic — in this case, communicating with a REST API.

    Once those responsibilities are clearly separated, it becomes much easier to design the plugin, troubleshoot failures, and add meaningful logging without turning the state machine into a collection of API-specific details.

    In a future article, I may take this architecture one step further and build a practical TPC plugin example from beginning to end, including the Process.ini, Prompts.ini, PowerShell script, API error handling, and troubleshooting through TPC traces.

    Official Reference

    CyberArk documentation:

    Terminal Plugin Controller (TPC)
    CyberArk — Terminal Plugin Controller

  • Why Shouldn’t the Idira (CyberArk) Digital Vault Be on the Same Network as Everything Else?


    If you’ve worked with Idira (formerly CyberArk) for a while, you’ve probably heard this recommendation before:

    “The Digital Vault should not be joined to the domain and should be isolated from the corporate network.”

    Most people simply accept that recommendation because “it’s in the documentation.”

    But sooner or later, almost every customer asks the same question:

    “Why?”

    Honestly, it’s a fair question.

    At first glance, it doesn’t seem to make much sense. After all, every other server in the environment lives happily inside the corporate network. Why should the Vault be treated differently?

    The answer becomes much simpler if we stop thinking of the Vault as just another server.

    It’s not really a server…

    Imagine you own a bank.

    Inside the building, you have computers, printers, employee desks, meeting rooms, and ATMs.

    Now think about where the money is kept.

    It isn’t sitting next to someone’s desk.

    It’s locked inside a heavily protected vault, separated from everything else.

    The reason is obvious.

    If someone manages to enter the building, you don’t want them to immediately reach the place that contains the most valuable assets.

    The Digital Vault follows exactly the same idea.

    It stores the credentials that protect your entire organization.

    Administrator passwords.

    Service accounts.

    Application secrets.

    Certificates.

    Encryption keys.

    Audit information.

    In many environments, if someone gains full control of the Vault, they can potentially gain access to almost everything else.

    That’s why it deserves a different level of protection.


    What happens if everything lives together?

    Imagine a company where the following servers all share the same network:

    • Active Directory
    • SQL Server
    • File Server
    • Web Servers
    • PVWA
    • CPM
    • PSM
    • Digital Vault

    Everything looks organized.

    Everything communicates easily.

    Everything is simple.

    Now imagine a web server gets compromised because someone forgot to install a security update.

    It happens every day.

    The attacker now has a foothold inside your network.

    From there, they begin exploring.

    They scan the network.

    They identify other servers.

    They try connecting to different services.

    They attempt lateral movement.

    Eventually, they discover the Digital Vault.

    Now, to be clear, discovering the Vault doesn’t mean they’ve compromised it.

    The Vault was specifically designed to resist attacks.

    But here’s the important point:

    Why make it easier for an attacker to even find it?

    Security isn’t only about building strong walls.

    It’s also about making those walls harder to reach.


    Security is about reducing opportunities

    One lesson I’ve learned working with PAM solutions is that good security isn’t only about blocking attacks.

    It’s about reducing the number of opportunities attackers have.

    Think about your own house.

    You probably lock your front door.

    But you probably also close your windows when you leave.

    Not because you expect someone to break in every day…

    …but because removing opportunities is always a good idea.

    The Digital Vault follows exactly the same philosophy.

    Instead of allowing every server in the company to potentially communicate with it, only a very small number of systems should ever know it exists.

    Less visibility.

    Less exposure.

    Less risk.


    Why doesn’t the Vault join the Active Directory domain?

    This is another recommendation that often surprises customers.

    “We already trust Active Directory.”

    “So why shouldn’t the Vault trust it too?”

    The answer is actually pretty interesting.

    The Vault is designed under the assumption that one day your corporate infrastructure could be compromised.

    Notice I didn’t say will.

    I said could.

    Because that’s exactly how security architects think.

    If an attacker gains Domain Admin privileges, they can usually control almost every Windows server in the environment.

    Group Policies.

    Authentication.

    Administrative access.

    DNS.

    Certificates.

    All of these become potential attack paths.

    By keeping the Vault outside the domain, you’re creating one more security boundary.

    Even if the domain is compromised, the Vault isn’t automatically affected.

    It’s one less dependency.

    And in security, fewer dependencies usually mean fewer problems.


    Why avoid DNS?

    I’ll admit this one confused me the first time I read the recommendation.

    Running a server without DNS feels almost wrong.

    But then it starts making sense.

    DNS is another service your infrastructure depends on.

    If DNS is unavailable, servers may struggle to find each other.

    If DNS is compromised, traffic could potentially be redirected somewhere it shouldn’t go.

    The Digital Vault doesn’t really need dynamic name resolution.

    Its communication is very controlled and very predictable.

    Using static IP addresses and a carefully managed hosts file removes yet another dependency from the equation.

    Again…

    The goal isn’t convenience.

    The goal is resilience.


    Assume the network is already compromised

    One of the biggest mindset shifts in cybersecurity is this:

    Don’t build your defenses assuming nothing will ever go wrong.

    Build them assuming something eventually will.

    That’s the philosophy behind many modern security frameworks, including Zero Trust.

    The Digital Vault follows that same mindset.

    If someone compromises a workstation…

    the Vault should still be protected.

    If someone compromises Active Directory…

    the Vault should still be protected.

    If someone compromises DNS…

    the Vault should still be protected.

    It’s not about distrust.

    It’s about planning for bad days.


    The Vault isn’t isolated because it’s fragile.

    It’s isolated because it’s valuable.

    That’s a very important distinction.

    Sometimes people assume isolation exists because the Vault can’t handle attacks.

    Actually, it’s almost the opposite.

    The Vault is one of the most hardened components in the entire PAM solution.

    The isolation exists because it protects the most valuable secrets in your organization.

    And when something is that valuable, giving it an extra layer of protection simply makes sense.

    Just like we don’t keep cash in the reception area of a bank…

    we shouldn’t treat the Digital Vault like an ordinary server.


    Final thoughts

    One thing I really like about the Idira (CyberArk) architecture is that many of its recommendations stop making sense if you only look at them individually.

    Don’t join the domain.

    Avoid DNS.

    Isolate the Vault.

    Restrict network access.

    At first, they can seem overly cautious.

    But once you step back and think about what the Digital Vault actually represents, the picture becomes much clearer.

    It isn’t just another server.

    It’s the place where the keys to your entire infrastructure are stored.

    And if there’s one lesson security has taught us over the years, it’s this:

    The strongest lock in the world doesn’t help much if you leave the vault in the middle of the lobby.