Proper Power Platform ALM: Moving from Manual ZIP Files to PAC CLI and Pipelines

A working reference for the Power Platform CLI solution commands, and the migration path off hand carried solution ZIP files: what export, import, pack, unpack, clone, sync, version, check and create-settings each do, which of them belong on a laptop and which belong only on a build agent, and how to get an environment that already has years of direct changes onto a reviewable release path.

Stop moving Power Platform and Dynamics 365 changes by exporting a solution ZIP and importing it by hand. Proper application lifecycle management uses the Power Platform CLI, which is the tool you invoke as pac. Four commands carry most of the work: pac solution export pulls a solution out of an environment as a ZIP, pac solution unpack explodes that ZIP into hundreds of readable files you commit to Git, pac solution pack rebuilds a ZIP from those committed files, and pac solution import pushes it into the next environment. Development stays unmanaged, test and production only ever receive managed solutions, and a pipeline runs the pack and import steps so no human ever carries a file between environments again.

Two notes before the commands. First, the Power Platform CLI is versioned and it changes often: flags get added, long forms get short aliases, defaults shift, and a few commands that were preview when a tutorial was written are now general availability with different names. Treat every command below as the shape of the operation rather than as a string to paste, and confirm the exact flags for your installed version with pac help solution and with pac solution export --help before you put anything in a pipeline. Second, this page is about the operation rather than the installation. The CLI ships in three forms, as a dotnet tool, as a Visual Studio Code extension, and as a standalone Windows install, and which one you have decides how you upgrade it but changes nothing about the commands themselves.

The answer in three lines

The ZIP is a build output, not the thing you are keeping

A solution ZIP is one opaque binary that a diff tool cannot read, a reviewer cannot review, and a merge cannot merge. pac solution unpack turns it into a directory tree of XML, JavaScript, plugin assemblies and canvas app sources, one file per component, and that tree is what belongs in your repository. From then on the ZIP is regenerated by pac solution pack whenever you need one, in the same way you would never commit a compiled binary and then edit it.

Export pairs with unpack, pack pairs with import

Those two pairings are the whole mental model. Coming out of an environment you run export then unpack, which gets platform changes into source control. Going into an environment you run pack then import, which gets committed source into a target. Once the repository exists, pac solution clone and pac solution sync collapse each pair into a single command, and once a pipeline exists, nobody runs the second pair by hand at all.

Unmanaged in development, managed everywhere else, and never both

A change is made in an unmanaged solution in development, and it reaches test and production only as a managed solution imported by a pipeline. That single rule is what makes a change reversible, because a managed component can be uninstalled and an unmanaged one cannot. Environments that receive unmanaged imports accumulate layers nobody can remove, which is the condition most of the environments we are asked to rescue are actually in.

The pac solution commands, in the order you actually run them

Grouped by the sequence rather than alphabetically, because the sequence is the part a command reference cannot give you. Flag names and defaults move between CLI releases, so confirm each one against your installed version with pac help solution before it goes into a pipeline.

CommandWhat it doesWhen you run it
pac auth createCreates a stored authentication profile against one environment, interactively for a person or with an application id, client secret and tenant for a build agent.Once per environment on a workstation, and once per run on a build agent using a service principal rather than a named user.
pac auth list / pac auth selectLists the stored profiles and switches the active one. pac org who confirms which environment and which user you are currently acting as.Before every command that writes. This is the cheapest habit on the page and it is the one that prevents importing into the wrong environment.
pac solution listLists the solutions installed in the connected environment with their unique names and versions.At the start of any takeover, to find out what the environment actually contains rather than what you were told it contains.
pac solution exportDownloads a solution from the connected environment as a ZIP. Takes the solution unique name, an output path, and whether the artifact is managed or unmanaged.Whenever changes made in the maker portal need to come back into source control. Always exported unmanaged from development.
pac solution unpackExplodes a solution ZIP into a folder of individual component files. The processCanvasApps option additionally unpacks canvas app sources so they are diffable rather than a single binary.Immediately after every export. The unpacked folder, not the ZIP, is what you commit.
pac solution packThe inverse of unpack. Rebuilds a solution ZIP from an unpacked folder, as managed or unmanaged, so the artifact is produced from committed source rather than from somebody workstation.In the build stage of the pipeline, every time, and almost never by hand.
pac solution importImports a solution ZIP into the connected environment, with options to publish customizations, activate plugins, run asynchronously, force an overwrite of unmanaged customizations, and apply a deployment settings file.In the release stage of the pipeline. Managed only, into test and then production, and never as a manual step by a person with a browser.
pac solution cloneExport and unpack in one command, and it also creates the project files so the folder can be built as a repeatable artifact.Once, at the start, to take the baseline of a solution that already exists in an environment and has never been in source control.
pac solution syncRefreshes an already cloned local folder with the current state of the environment, so the change set appears as a diff in your working tree.Every working day, on a branch, after somebody has made changes in the development environment. This is the command that replaces routine exporting.
pac solution init / pac solution add-referenceCreates a new solution project with a publisher name and prefix, and adds a reference to a code project such as a plugin or a PCF control so the build produces one artifact containing both.When starting a new solution, or when code components need to ship inside the same solution as the configuration that uses them.
pac solution versionIncrements the solution version in the project, so the build number is stamped in one place rather than typed into a form by whoever is deploying.In the build stage, before pack. An unincremented version is the most common cause of an import that succeeds and changes nothing.
pac solution checkRuns the Power Platform solution checker against a packed artifact and writes the results out, so static analysis findings can fail a build.In the build stage, after pack and before anything touches an environment. Set the severity that fails the build and agree it with the team once.
pac solution create-settingsGenerates a deployment settings file listing every environment variable and connection reference the solution contains, ready to be filled in per environment.Once per solution, then again whenever a new environment variable or connection reference is added. Pass the filled file to import.
pac solution upgradeApplies a staged solution upgrade, which is the path that removes components deleted since the previous version rather than leaving them behind.When a release deletes components. A plain import updates and adds but does not remove, which is how orphaned columns and views accumulate.
pac data export / pac data importMoves configuration data, meaning reference records rather than transactional records, between environments using a schema file that names the tables and columns.Whenever a solution depends on records rather than only on metadata. Configuration data belongs in the pipeline next to the solution, not in a spreadsheet.

Authenticate once per environment, and check before you write

The profiles are stored on the machine and persist between sessions, which is convenient and is also exactly why the active one needs checking. The worst accident available in this stack is an import that runs against production while the person running it believes they are pointed at a sandbox.

Authentication profiles
# One named profile per environment. You will have several, so name them.
pac auth create --name dev  --environment https://contoso-dev.crm4.dynamics.com
pac auth create --name test --environment https://contoso-test.crm4.dynamics.com
pac auth create --name prod --environment https://contoso.crm4.dynamics.com

# Confirm which one you are pointed at before every command that writes.
pac auth list
pac auth select --name dev
pac org who

Take the baseline: export and unpack, or clone

This is the one time operation that turns a solution which has only ever existed inside an environment into source you can read, review and merge. Run it against development, never against production, and commit the unpacked folder rather than the ZIP.

First move: the solution into source control
# One time only. Take the solution that already lives in development
# and put its source, file by file, into the repository.
pac auth select --name dev
pac solution list

# The explicit pair, which is worth running once so you can see both halves.
pac solution export --name ContosoSales \
  --path ./artifacts/ContosoSales.zip --managed false
pac solution unpack --zipfile ./artifacts/ContosoSales.zip \
  --folder ./src/ContosoSales --packagetype Unmanaged --processCanvasApps

# Or the same thing in one command, which also writes the project files.
pac solution clone --name ContosoSales --outputDirectory ./src

git add src/ContosoSales
git commit -m "Baseline: ContosoSales exactly as development holds it today"

The daily loop: sync onto a branch and read the diff

Makers keep working in the maker portal exactly as before. What changes is that their work becomes a reviewable change set on a branch instead of an invisible edit to a live environment, and this is the point where the method starts paying for itself rather than costing.

Every working day
# Somebody changed things in the development environment this week.
# Pull them onto a branch and read the diff before you commit anything.
git checkout -b feature/quote-approval
pac solution sync --solution-folder ./src/ContosoSales
git diff

# The diff is the code review. A column nobody asked for, a form section added
# during a demo, a flow left switched on by a developer: all of it is visible
# here, which is the point. It stops on the branch rather than in production.
git add -A
git commit -m "Quote approval: two columns, a form section and the approval flow"

The pipeline: pack from committed source, check, then import managed

Written as raw pac commands rather than as tasks, because the tasks in Azure DevOps and the actions in GitHub wrap these same operations and it is worth seeing what they are doing. Note the two things a person can no longer get wrong here: the version is stamped by the build, and only the managed artifact travels.

Build and release stages
# Build agent. No person, no browser, no ZIP file on anybody laptop.
pac auth create --name build \
  --environment https://contoso-build.crm4.dynamics.com \
  --applicationId $APP_ID --clientSecret $CLIENT_SECRET --tenant $TENANT_ID

# Stamp the build number on, then produce both artifacts from one committed source.
pac solution version --patchversion $BUILD_NUMBER
pac solution pack --zipfile ./out/ContosoSales.zip \
  --folder ./src/ContosoSales --packagetype Unmanaged
pac solution pack --zipfile ./out/ContosoSales_managed.zip \
  --folder ./src/ContosoSales --packagetype Managed

# Gate the build on static analysis before anything reaches an environment.
pac solution check --path ./out/ContosoSales_managed.zip \
  --outputDirectory ./checkresults

# Release stage. Managed only, into test first, then production on approval.
pac auth select --name test
pac solution import --path ./out/ContosoSales_managed.zip \
  --settings-file ./config/test.deploymentsettings.json \
  --activate-plugins --publish-changes --force-overwrite --async

Deployment settings: the values that must differ per environment

The step that separates a pipeline which works from a pipeline which is safe. Without it, a solution exported from development carries development endpoints and development identities into production, and does so silently.

Environment variables and connection references
# Generate the template once from the packed managed artifact.
pac solution create-settings --solution-zip ./out/ContosoSales_managed.zip \
  --settings-file ./config/deploymentsettings.json

# It lists every environment variable and every connection reference in the
# solution, which is exactly the set of values that must differ per environment.
# Commit one filled copy per environment, with secrets left as placeholders
# that the pipeline substitutes from its own secret store at release time.

pac auth select --name prod
pac solution import --path ./out/ContosoSales_managed.zip \
  --settings-file ./config/prod.deploymentsettings.json --async

What manual export and import actually costs you

Not an argument from best practice. These are the specific, recurring failures that bring a system to us, roughly in the order of how much damage each one does by the time somebody calls.

Nobody can tell you what changed, or who changed it

The single most expensive property of a hand carried ZIP is that it has no history. A binary in a shared folder called ContosoSales_v4_final_FIXED.zip records neither what is inside it nor what it differs from. An unpacked solution in Git records both, at the level of a single form field, a single option set value, or a single line of a plugin. Almost every question that gets asked in an incident, which is what changed and when and why, becomes a git log rather than an investigation.

Two developers overwrite each other and neither of them notices

Export is a whole solution operation. If one developer exports at ten in the morning and another exports the same solution at two in the afternoon, the second ZIP silently contains a snapshot of everything, including a half finished change the first developer had not intended to promote. Nothing warns anybody. Unpacked source with branches and pull requests makes this a merge conflict, which is a conversation, rather than a deployment, which is an incident.

A production import goes in unmanaged and can never be taken out

This is the mistake with the longest tail. An unmanaged component imported into production becomes part of the base layer of that environment permanently. There is no uninstall. The only routes back are a restore from backup or reversing the change by hand, component by component, and the second one is what most teams end up doing. Managed imports, by contrast, sit in a removable layer, and a bad release can be rolled back by reinstalling the previous managed artifact.

The environment specific values are baked into the file

A solution exported from development carries development values: the SharePoint site the flow points at, the endpoint a custom connector calls, the account an integration authenticates as, the record identifiers a plugin was configured with. Imported into production without intervention, the production system quietly reads and writes development data. The fix is environment variables and connection references, resolved at import time from a deployment settings file, and the CLI generates that file for you with pac solution create-settings.

The version number stops meaning anything

Manual promotion almost always drifts on versioning, because incrementing the version is a step somebody has to remember and nothing enforces. Then two environments both claim to hold 1.0.0.4 and they do not contain the same thing. Worse, an import of a solution whose version was not incremented can leave cached artifacts in place while the import log reports success, which produces the specific and maddening symptom of a change that deployed cleanly and did not appear.

The person who knows the import order leaves

Real systems are several solutions with dependencies between them, plus configuration data, plus a set of manual post deployment steps that live in somebody head or in a document last updated a year ago. That knowledge is a staffing risk rather than a technical one right up until the week it becomes both. A pipeline definition in the repository is the same knowledge written in a form that executes, which means it cannot quietly go stale without the build failing.

Testing happens in production because there is nowhere else

When promotion is expensive and manual, teams naturally do less of it, and the honest end state of that is changes made directly in the live environment because getting them there properly takes an afternoon. Every environment we are asked to look at where customizations were made in production got there by this route. It is not a discipline problem. It is what happens when the correct path costs an hour and the incorrect one costs five minutes.

Rollback is a conversation about backups rather than a command

Ask a team using manual promotion what happens if tonight release is wrong, and the answer is usually a restore, which means losing the day of business data as well as the bad release. With managed solutions built from tagged source, the answer is that the previous artifact is still in the build store and reinstalling it is a pipeline run. The difference between those two answers is the entire reason the finance director eventually agrees to pay for this work.

The layered, unreviewable configuration these habits produce is the same condition we describe from the other end on our Dynamics 365 customization mistakes page, and when it has gone far enough that nobody can safely change anything, it becomes a project rescue and takeover.

The migration, step by step

Steps 1 to 3 are decisions that are cheap now and expensive in a year. Steps 4 to 7 put the source in a repository and make the diff the review, which is where most of the benefit lives. Steps 8 to 10 automate the promotion. Steps 11 and 12 are the parts teams skip and then regret, which are reconciling what is already in production and naming an owner. The order matters more than the tooling does.

  1. Inventory what the environments actually contain

    Connect to production with pac auth create and run pac solution list, then do the same against test and development. You are looking for three things: how many solutions there are and whether they are managed or unmanaged, whether the same solution carries the same version in each environment, and how much sits in the default solution, which is where everything made directly in an environment ends up. Write the answer down before you decide anything, because the plan for an environment with three managed solutions and a clean default is completely different from the plan for one with eleven unmanaged solutions and two years of direct edits.

  2. Settle the publisher and the prefix, once, before anything else

    Every component carries the prefix of the publisher that created it, and changing a publisher later does not rename anything: it creates new components alongside the old ones. So this is the cheapest decision on the page today and one of the most expensive in a year. Pick one publisher for your organisation, give it a short prefix, use it in every environment, and make sure the maker portal default publisher is set to it so that anything created in a hurry still lands in the right place.

  3. Decide the solution boundaries deliberately

    One enormous solution containing everything makes every deployment a deployment of everything, and imports get slower until nobody wants to run one. A separate solution per developer makes dependency order a permanent puzzle. The shape that works for most mid sized systems is a small number of solutions along real boundaries: the core data model, one per functional area that ships independently, and one for code components such as plugins and PCF controls. Dependencies must run one way only, and it is worth drawing them on paper because a cycle is much easier to see before it exists.

  4. Install the CLI and create one authentication profile per environment

    Install the Power Platform CLI, then run pac auth create against development, test and production and give each profile a name. Get into the habit of running pac auth list and pac org who before any command that writes. This sounds trivial and it is the single most common cause of the worst kind of accident in this stack, which is running an import against production while believing you are pointed at a sandbox.

  5. Take the baseline into source control with clone, or with export and unpack

    Run pac solution clone against the development environment, which exports the solution and unpacks it into a folder of individual component files in one step, and also writes the project files needed to build it later. If you would rather see both halves, run pac solution export with the managed flag set to false, then pac solution unpack on the resulting ZIP, adding the option that also unpacks canvas app sources so they become diffable. Commit the unpacked folder. Do not commit the ZIP, and add the artifact folder to gitignore, because the ZIP is a build output from this point on.

  6. Make the diff the review, using sync on a branch

    From now on, work happens in the development environment as it always did, and pac solution sync brings it down onto a branch as a readable change set. The pull request diff becomes the review, which is where this method starts paying for itself: a column nobody asked for, a business rule left switched on after a demo, a form change made during a workshop and forgotten, all of them become visible before they travel. Agree with the team that the branch is the unit of work and that nothing reaches test without going through one.

  7. Remove environment specific values with environment variables and connection references

    Every value that differs between environments has to come out of the components and into an environment variable or a connection reference: endpoint URLs, site addresses, feature switches, the identity an integration authenticates as. Then run pac solution create-settings against the packed artifact to generate a deployment settings file listing all of them, and keep one filled copy per environment in the repository, with secrets left as placeholders that the pipeline substitutes at release time from its own secret store. Skipping this step is the reason most first pipelines deploy successfully and then read development data in production.

  8. Build the artifacts on a build agent, never on a workstation

    The build stage checks out the committed source, runs pac solution version to stamp the build number, then runs pac solution pack twice to produce the unmanaged and managed artifacts from that same source. Both are published as build artifacts and kept. This is the step that makes the whole thing auditable, because from here on every artifact that exists is traceable to a commit, and the question of what is running in production has an answer that does not require anybody to remember anything.

  9. Gate the build on the solution checker

    Run pac solution check against the packed managed artifact and have the pipeline fail the build above an agreed severity. The value is not that the checker is always right, because it is not, and every team accumulates a small list of rules it deliberately suppresses with a reason written next to each one. The value is that the list is explicit and reviewed rather than implicit and forgotten, and that a genuinely dangerous pattern gets caught before somebody has to read it in an incident report.

  10. Release managed only, into test first, with an approval before production

    The release stage authenticates as a service principal, then runs pac solution import against test with the settings file for test, publishing changes and activating plugins, and running asynchronously so a large solution does not time out. Production is the same command with the production settings file behind a manual approval. Use pac solution upgrade rather than a plain import for releases that delete components, because an import updates and adds but does not remove, and that difference is how orphaned columns, views and processes accumulate in a long lived environment.

  11. Deal with the unmanaged layers that are already in production

    Almost every environment moving to this method has direct changes in production that exist nowhere else, and pretending otherwise is how a first pipeline run overwrites something the business depends on. Find them, decide one at a time whether each is wanted, rebuild the wanted ones properly in development so they arrive through the pipeline, and remove the rest. This is genuinely the slowest part of the work and it is not optional, because until it is done the repository is not actually the source of truth and everyone is relying on a promise that it is.

  12. Put a name against the pipeline and rehearse the rollback

    A pipeline nobody owns rots in about a quarter: a secret expires, a service principal loses a role, an agent image changes, and the team quietly goes back to carrying ZIP files while telling itself this is temporary. Name an owner, put the credential expiry dates in a calendar, and rehearse a rollback once by reinstalling the previous managed artifact into test on purpose. Doing that once, in daylight, is worth considerably more than a document describing how it would work.

If step 1 turns up more than you expected, and it usually does, our Dynamics 365 health check is the fixed scope version of that inventory, covering solution layering, plugins, scripts and environment topology in one pass.

Which pipeline, and whether you need one yet

Pipelines is assumed to mean Azure DevOps, and for a lot of teams it should not. The choice matters much less than whether the solution source is in a repository at all, and it is reversible, so it deserves an afternoon rather than a month.

Power Platform pipelines, configured in the platform itself

Microsoft ships a pipelines capability inside the platform that promotes solutions between environments with approvals, without anybody writing YAML or maintaining an agent. For a team whose changes are configuration rather than code, and whose bottleneck is that promotion is manual rather than that promotion is untested, this is very often the right first move and it can be running in a day. Its limits are real: less room for custom build steps, less control over what gates a release, and a weaker story when you need the same commit to build a plugin assembly and run a test suite on the way through.

Azure DevOps pipelines with the Power Platform build tools

The most common choice on larger systems, and the one to pick when the work already lives in Azure DevOps or when solutions ship alongside C# plugins, PCF controls or integration code. There are supplied tasks that wrap the same operations described on this page, and you can also call pac directly, which we tend to do for anything the tasks do not cover so that the pipeline reads the same as the commands a developer runs locally. Service connections handle authentication, environments handle approvals, and the artifact retention gives you the previous release to roll back to.

GitHub Actions

Functionally equivalent for this purpose, and the natural choice when the source already sits in GitHub. There are published actions covering export, pack, import and the solution checker, authentication uses a service principal held in repository or environment secrets, and environment protection rules give you the approval gate before production. The tradeoff against Azure DevOps is mostly about which tool your organisation already administers rather than about capability.

Source control with no pipeline at all, as a deliberate first stage

Worth naming explicitly because it is the step most teams should take first and skip. Getting the solution unpacked into Git and running sync on a branch delivers a large share of the total benefit, which is history, review and the ability to answer what changed, and it can be done in an afternoon without any infrastructure. The promotion can stay manual for a few weeks while people get used to the diff being the review. Automating promotion into a repository nobody yet trusts just makes the wrong thing happen faster.

What goes wrong on the first pipeline

None of these are in the command reference, because none of them are properties of a command. They are the things that make a correctly written pipeline produce a wrong outcome, and every one of them is something we have been called in to unpick.

Committing the ZIP as well as the unpacked folder

It seems harmless and it quietly defeats the purpose. Two representations of the same thing will drift, reviewers start reading whichever one is easier, and eventually somebody builds from the committed ZIP rather than from source. Put the artifact directory in gitignore on day one and keep exactly one source of truth.

Importing unmanaged into test or production because it was quicker

Usually done once, under deadline pressure, with the intention of tidying it afterwards. It cannot be tidied. An unmanaged component in a target environment becomes part of its base layer permanently and the managed solution can no longer control it. This one exception is how a large share of the environments we are asked to rescue reached the state they are in.

Forgetting that a plain import never deletes anything

Remove a column in development, promote, and the column is still in production. Import updates and adds. Deletions travel only through a staged upgrade, which is what pac solution upgrade is for. Teams that do not know this accumulate orphaned components for years and then discover them during a data migration, which is the worst possible moment.

Not incrementing the version, then debugging the wrong thing

An import of an unchanged version number can complete successfully while the platform continues serving what it already had. The symptom is a clean import log and no visible change, and people lose entire days to it. Stamping the version in the build stage rather than trusting anybody to remember removes the failure mode altogether.

Authenticating the pipeline as a person

It works on the first run, which is why it happens. Then the person changes their password, or leaves, or their account gets a conditional access policy, and the release pipeline fails at the worst moment with an error about authentication that takes an hour to interpret. Pipelines authenticate as a service principal with an application id, a client secret or certificate, and the minimum roles it needs, and the expiry date of that secret goes in a calendar.

Canvas apps and flows treated as if they diff like configuration

Unpacked canvas app sources are readable but noisy, and a trivial edit can produce a large diff, so reviewers need to know what to skim and what to actually read. Flows carry connection references that must be mapped per environment or the first import into test fails with an error that names an identifier rather than a flow. Both are manageable and both surprise teams on their first pipeline run.

Configuration data left out of the pipeline

Reference records, meaning the rows the solution logically depends on rather than transactional data, are not part of a solution and do not travel with it. If they move by spreadsheet, then the pipeline is only automating half the release and the half it is not automating is the half that breaks silently. pac data export and pac data import with a schema file put that half in the pipeline where it belongs.

Starting with the pipeline instead of with the repository

The pipeline is the visible part, so it is where the enthusiasm goes. But a pipeline promoting a repository that does not yet match production is an automated way of deploying a fiction. Get the baseline right, reconcile the unmanaged layers, let the team live with the diff as a review for a couple of weeks, and then automate. The order matters more than the tooling choice does.

Code components have their own version of the same discipline, since a PCF control ships inside a solution and carries a manifest version of its own. The packaging and promotion path for those is set out on our Power Platform developer page, alongside the release checklist we run on every control.

How Solzet sets this up

Release engineering is part of how we deliver rather than something we sell separately, so this is what it looks like when we do it, on a new build and on a system that already exists.

We do this as part of delivery rather than as a separate product

Every environment we build gets separate development, test and production, unpacked solution source in your repository, and a build and release pipeline, and it is settled in the first week rather than added when the first release goes wrong. It is the least interesting decision on a project and the one whose absence does the most damage, which is why we do not offer a version of the engagement without it.

We retrofit it onto systems that already exist

The harder and more common job. An environment with years of direct changes, several unmanaged solutions, no repository and a promotion process that is one person and a shared folder. The work is inventory, publisher and prefix reconciliation, solution boundaries, the baseline into Git, the deployment settings, the pipeline, and then the slow part, which is deciding one at a time what to do with each unmanaged change already sitting in production.

It runs in your tooling, not ours

Azure DevOps, GitHub, or the in product pipelines, in your tenant, under your accounts, with the repository in your organisation. We work inside what you already administer rather than asking you to adopt something new, and the pipeline definitions are yours in the statement of work along with the solution source, so nothing about this arrangement is difficult to end.

Then somebody owns it after we leave

A pipeline with no owner degrades within a quarter. We hand over with the definitions in your repository, the credential expiry dates written down, a rehearsed rollback, and either a named person on your side or a monthly support allocation with us that covers the release path along with the two Microsoft release waves a year. Which of those it is should be decided before go live rather than found out afterwards.

The engagement models, certifications and what the wider role covers are on our Power Platform developer and Dynamics 365 developer pages. If the release path is one symptom of a project that has already stalled, the rescue and takeover service page describes how we stabilise an environment first and put it on a controlled release path second.

Frequently Asked Questions

What are the main Power Platform CLI pac solution commands for export, import, pack and unpack?

Six commands cover almost everything. pac solution export downloads a solution from the connected environment as a ZIP, taking the solution unique name, an output path, and whether it is managed or unmanaged. pac solution unpack explodes that ZIP into a folder of individual component files, with an option that also unpacks canvas app sources so they are diffable. pac solution pack is the inverse, rebuilding a ZIP from an unpacked folder as managed or unmanaged. pac solution import pushes a ZIP into the connected environment, with options to publish customizations, activate plugins, run asynchronously and apply a deployment settings file. pac solution clone does export and unpack in one step and also writes the project files, which is what you use for the first baseline. pac solution sync refreshes an already cloned folder from the environment, which is what you use every day afterwards. Around those sit pac auth create for authentication, pac solution version for the build number, pac solution check for static analysis and pac solution create-settings for the per environment values. Flags change between CLI releases, so confirm yours with pac help solution.

What is the difference between pac solution export and pac solution unpack?

They are consecutive halves of the same operation and people conflate them constantly. Export is a server operation: it asks the environment for a solution and gives you a ZIP file, which is a single binary that no diff tool can read and no reviewer can review. Unpack is a purely local operation on that file: it takes the ZIP and writes out a folder tree with one file per component, so a form is a file, an option set is a file, a plugin step is a file. Export gets the change out of the platform, unpack makes it reviewable and mergeable. Committing the output of export rather than the output of unpack is the most common way teams end up with version control that gives them almost none of the benefit of version control.

Should I use pac solution clone or pac solution export followed by unpack?

Clone once, then sync from then on, and reach for the explicit pair when you need control. Clone runs export and unpack together and additionally creates the project files that let the folder be built as a repeatable artifact, which makes it the right command for taking the first baseline of a solution that has never been in source control. After that, sync refreshes the cloned folder from the environment and gives you the change set as a diff in your working tree, which is the daily loop. The explicit export and unpack pair is still worth knowing and still worth running once, because it is what the pipeline and the tooling are doing underneath, and because there are situations, such as exporting with specific included settings or against a target version, where you want each half separately.

Can I keep exporting and importing solutions by hand if the team is small?

You can, and for a single developer on a single small solution it is a defensible choice for a while. The point at which it stops being defensible is not a headcount, it is the arrival of any one of four things: a second person making changes, a production environment that real money depends on, an auditor or a customer asking what changed and when, or the first release that has to be rolled back. Each of those turns a hand carried ZIP from an inconvenience into a risk. The cheap middle position is worth naming: get the solution unpacked into Git and use sync on a branch, which takes an afternoon and delivers history and review, and leave promotion manual for a while longer. That gets you most of the benefit before you have written a line of pipeline.

What is the difference between managed and unmanaged solutions, and why does it matter here?

Unmanaged is the editable state, which is why development environments hold unmanaged solutions and why that is the only place components should be edited. Managed is the sealed state: the components are locked to the solution that installed them, and crucially the whole thing can be uninstalled, which is what makes a release reversible. The rule that follows is simple and should never be broken, not even once under deadline pressure. Development is unmanaged, test and production only ever receive managed. The reason it should never be broken is that an unmanaged component imported into a target becomes part of the base layer of that environment permanently, with no uninstall available, and the only routes back are a restore from backup or undoing it by hand. That single exception, made once, is how a large share of the environments we are asked to take over reached the state they are in.

How do I stop development values reaching production when the pipeline deploys?

Take them out of the components. Every value that differs between environments becomes an environment variable, and every connector a flow uses becomes a connection reference, so that no component contains an endpoint, a site address or an identity directly. Then run pac solution create-settings against the packed artifact to generate a deployment settings file listing all of them, keep one filled copy per environment in the repository, and pass the right one to pac solution import at release time. Secrets stay as placeholders in the committed file and are substituted by the pipeline from its own secret store. Teams that skip this get a first pipeline that deploys perfectly and then quietly reads and writes development data from production, which typically goes unnoticed for a fortnight.

Why did my solution import succeed but the change is not there?

The first thing to check is the version number, because an import of a solution whose version was not incremented can complete successfully while the platform continues serving what it already had. Stamping the version in the build stage with pac solution version removes that failure mode entirely. The second thing is publishing, since some changes are inert until customizations are published, which is what the publish changes option on import is for. The third is that a plain import updates and adds but never deletes, so if what you expected was a removal rather than a change, it will not have happened and you need a staged upgrade instead. And the fourth, which is less common but wastes the most time, is a dependency that exists in the development environment but not in the target, so a component imported without the thing it depends on and behaves as though it is not there.

Do I need Azure DevOps, or can I use the pipelines built into the platform?

For many mid sized teams the in product pipelines feature is the right answer and it is available in a day, because it promotes solutions between environments with approvals and needs no YAML, no agent and no separate tool to administer. Choose Azure DevOps or GitHub Actions when the release has to do more than move a solution: build a plugin assembly or a PCF control from the same commit, run tests, run the solution checker as a gate, deploy configuration data, or coordinate with something outside this platform. Also choose them when your organisation already runs one of the two, because the pipeline that gets maintained is the one sitting in the tool people already open. It is a reversible decision either way, and it is far less important than whether the solution source is in a repository at all.

We already have years of changes made directly in production. Where do we start?

Not with the pipeline. Start with pac solution list against every environment and an honest inventory of what each one holds, including how much sits in the default solution, which is where anything made directly in an environment ends up. Then reconcile the publisher and prefix, decide the solution boundaries, and take a baseline of development into Git. The slow part, and it is genuinely slow, is going through the changes that exist only in production and deciding one at a time whether each is wanted, rebuilding the wanted ones properly in development so they arrive through the pipeline, and removing the rest. Until that is done the repository is not the source of truth and a pipeline is only automating a fiction. This is the shape of work we do as a project rescue, and it is described on our project rescue and takeover page.

Send us the output of pac solution list

Run it against production and against development and send us both. From those two lists we can usually tell you how far apart your environments have drifted, whether anything unmanaged has been imported where it should not have been, and what the shortest route to a reviewable release path looks like for your system. Solzet is a Dynamics 365 Customer Engagement and Power Platform consultancy in Yerevan, Armenia, working with clients and Microsoft partners across Europe and the US.