diff --git a/arduino/cores/packagemanager/install_uninstall.go b/arduino/cores/packagemanager/install_uninstall.go
index cfcc0a21e46..112faea8340 100644
--- a/arduino/cores/packagemanager/install_uninstall.go
+++ b/arduino/cores/packagemanager/install_uninstall.go
@@ -16,6 +16,7 @@
 package packagemanager
 
 import (
+	"bytes"
 	"encoding/json"
 	"fmt"
 	"runtime"
@@ -174,9 +175,13 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools(
 		if !platformRelease.IsInstalled() {
 			return errors.New(tr("platform not installed"))
 		}
-		if err := pme.RunPostInstallScript(platformRelease.InstallDir); err != nil {
-			taskCB(&rpc.TaskProgress{Message: tr("WARNING cannot configure platform: %s", err)})
+		stdout, stderr, err := pme.RunPostInstallScript(platformRelease.InstallDir)
+		skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stdout), Completed: true})
+		skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stderr), Completed: true})
+		if err != nil {
+			taskCB(&rpc.TaskProgress{Message: tr("WARNING cannot configure platform: %s", err), Completed: true})
 		}
+
 	} else {
 		log.Info("Skipping platform configuration.")
 		taskCB(&rpc.TaskProgress{Message: tr("Skipping platform configuration.")})
@@ -226,7 +231,7 @@ func (pme *Explorer) cacheInstalledJSON(platformRelease *cores.PlatformRelease)
 
 // RunPostInstallScript runs the post_install.sh (or post_install.bat) script for the
 // specified platformRelease or toolRelease.
-func (pme *Explorer) RunPostInstallScript(installDir *paths.Path) error {
+func (pme *Explorer) RunPostInstallScript(installDir *paths.Path) ([]byte, []byte, error) {
 	postInstallFilename := "post_install.sh"
 	if runtime.GOOS == "windows" {
 		postInstallFilename = "post_install.bat"
@@ -235,14 +240,16 @@ func (pme *Explorer) RunPostInstallScript(installDir *paths.Path) error {
 	if postInstall.Exist() && postInstall.IsNotDir() {
 		cmd, err := executils.NewProcessFromPath(pme.GetEnvVarsForSpawnedProcess(), postInstall)
 		if err != nil {
-			return err
+			return []byte{}, []byte{}, err
 		}
+		cmdStdout, cmdStderr := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
+		cmd.RedirectStdoutTo(cmdStdout)
+		cmd.RedirectStderrTo(cmdStderr)
 		cmd.SetDirFromPath(installDir)
-		if err := cmd.Run(); err != nil {
-			return err
-		}
+		err = cmd.Run()
+		return cmdStdout.Bytes(), cmdStderr.Bytes(), err
 	}
-	return nil
+	return []byte{}, []byte{}, nil
 }
 
 // IsManagedPlatformRelease returns true if the PlatforRelease is managed by the PackageManager
@@ -335,7 +342,10 @@ func (pme *Explorer) InstallTool(toolRelease *cores.ToolRelease, taskCB rpc.Task
 	if !skipPostInstall {
 		log.Info("Running tool post_install script")
 		taskCB(&rpc.TaskProgress{Message: tr("Configuring tool.")})
-		if err := pme.RunPostInstallScript(toolRelease.InstallDir); err != nil {
+		stdout, stderr, err := pme.RunPostInstallScript(toolRelease.InstallDir)
+		skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stdout)})
+		skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stderr)})
+		if err != nil {
 			taskCB(&rpc.TaskProgress{Message: tr("WARNING cannot configure tool: %s", err)})
 		}
 	} else {
@@ -412,3 +422,12 @@ func (pme *Explorer) IsToolRequired(toolRelease *cores.ToolRelease) bool {
 	}
 	return false
 }
+
+func skipEmptyMessageTaskProgressCB(taskCB rpc.TaskProgressCB) rpc.TaskProgressCB {
+	return func(msg *rpc.TaskProgress) {
+		if msg != nil && len(msg.Message) == 0 {
+			return
+		}
+		taskCB(msg)
+	}
+}
diff --git a/arduino/cores/packagemanager/package_manager_test.go b/arduino/cores/packagemanager/package_manager_test.go
index 32dc4d3a23c..adecae80f28 100644
--- a/arduino/cores/packagemanager/package_manager_test.go
+++ b/arduino/cores/packagemanager/package_manager_test.go
@@ -19,6 +19,8 @@ import (
 	"fmt"
 	"net/url"
 	"os"
+	"runtime"
+	"strings"
 	"testing"
 
 	"github.com/arduino/arduino-cli/arduino/cores"
@@ -639,3 +641,39 @@ func TestLegacyPackageConversionToPluggableDiscovery(t *testing.T) {
 		require.Equal(t, `"{network_cmd}" -address {upload.port.address} -port {upload.port.properties.port} -sketch "{build.path}/{build.project_name}.hex" -upload {upload.port.properties.endpoint_upload} -sync {upload.port.properties.endpoint_sync} -reset {upload.port.properties.endpoint_reset} -sync_exp {upload.port.properties.sync_return}`, platformProps.Get("tools.avrdude__pluggable_network.upload.pattern"))
 	}
 }
+
+func TestRunPostInstall(t *testing.T) {
+	pmb := packagemanager.NewBuilder(nil, nil, nil, nil, "test")
+	pm := pmb.Build()
+	pme, release := pm.NewExplorer()
+	defer release()
+
+	// prepare dummy post install script
+	dir := paths.New(t.TempDir())
+
+	var scriptPath *paths.Path
+	var err error
+	if runtime.GOOS == "windows" {
+		scriptPath = dir.Join("post_install.bat")
+
+		err = scriptPath.WriteFile([]byte(
+			`@echo off
+			echo sent in stdout
+			echo sent in stderr 1>&2`))
+	} else {
+		scriptPath = dir.Join("post_install.sh")
+		err = scriptPath.WriteFile([]byte(
+			`#!/bin/sh
+			echo "sent in stdout"
+			echo "sent in stderr" 1>&2`))
+	}
+	require.NoError(t, err)
+	err = os.Chmod(scriptPath.String(), 0777)
+	require.NoError(t, err)
+	stdout, stderr, err := pme.RunPostInstallScript(dir)
+	require.NoError(t, err)
+
+	// `HasPrefix` because windows seem to add a trailing space at the end
+	require.Equal(t, "sent in stdout", strings.Trim(string(stdout), "\n\r "))
+	require.Equal(t, "sent in stderr", strings.Trim(string(stderr), "\n\r "))
+}