From fb6ead25eead3322e90500d592daa3a1d15910f3 Mon Sep 17 00:00:00 2001 From: Ivan Carlos de Almeida Date: Tue, 16 Dec 2025 04:43:41 -0300 Subject: [PATCH] first load --- .github/workflows/release_build.yml | 240 +++++++++++++ .github/workflows/update_readme.yml | 78 +++++ ADMIN-install-modules.ps1 | 76 +++++ LICENSE | 21 ++ POWERSHELL_ISSUE.md | 122 +++++++ README.md | 53 ++- mainscript.ps1 | 513 ++++++++++++++++++++++++++++ manifest.json | 4 + pendings.txt | 11 + tenantIds.txt | 1 + testing-guideline_en-US.md | 13 + testing-guideline_pt-BR.md | 13 + 12 files changed, 1144 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release_build.yml create mode 100644 .github/workflows/update_readme.yml create mode 100644 ADMIN-install-modules.ps1 create mode 100644 LICENSE create mode 100644 POWERSHELL_ISSUE.md create mode 100644 mainscript.ps1 create mode 100644 manifest.json create mode 100644 pendings.txt create mode 100644 tenantIds.txt create mode 100644 testing-guideline_en-US.md create mode 100644 testing-guideline_pt-BR.md diff --git a/.github/workflows/release_build.yml b/.github/workflows/release_build.yml new file mode 100644 index 0000000..fb79cd0 --- /dev/null +++ b/.github/workflows/release_build.yml @@ -0,0 +1,240 @@ +name: Build, Push, Publish + +on: + push: + branches: + - main + workflow_dispatch: + schedule: + - cron: '28 5 * * *' + workflow_run: + workflows: ["Sync Repo"] + types: + - completed + +jobs: + release: + name: Build & Release + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + + steps: + - name: 📥 Checkout code with full history and tags + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check if any tags exist + id: check_tags_exist + run: | + git fetch --tags + TAG_COUNT=$(git tag | wc -l) + if [ "$TAG_COUNT" -eq 0 ]; then + echo "has_tags=false" >> "$GITHUB_OUTPUT" + echo "latest_tag=v0.0.0" >> "$GITHUB_OUTPUT" + else + echo "has_tags=true" >> "$GITHUB_OUTPUT" + LATEST_TAG=$(git describe --tags --abbrev=0) + echo "latest_tag=$LATEST_TAG" >> "$GITHUB_OUTPUT" + fi + + - name: Check if meaningful commits exist since latest tag + id: check_commits + run: | + if [ "${{ steps.check_tags_exist.outputs.has_tags }}" = "false" ]; then + # No tags exist, so we should create first release + echo "commit_count=1" >> "$GITHUB_OUTPUT" + CHANGED_FILES=$(git ls-files | grep -v '^manifest.json$' || true) + if [ -n "$CHANGED_FILES" ]; then + echo "changed_files<> "$GITHUB_OUTPUT" + printf '%s\n' "$CHANGED_FILES" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + else + echo "changed_files=Initial release" >> "$GITHUB_OUTPUT" + fi + else + LATEST_TAG="${{ steps.check_tags_exist.outputs.latest_tag }}" + CHANGED_FILES="$(git diff --name-only "${LATEST_TAG}..HEAD" | grep -v '^manifest.json$' || true)" + if [ -n "$CHANGED_FILES" ]; then + echo "commit_count=1" >> "$GITHUB_OUTPUT" + echo "changed_files<> "$GITHUB_OUTPUT" + printf '%s\n' "$CHANGED_FILES" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + else + echo "commit_count=0" >> "$GITHUB_OUTPUT" + fi + fi + + - name: Get latest release tag (from GitHub API) + id: get_latest_release + run: | + LATEST_RELEASE_TAG=$(curl -sL -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/latest" | jq -r .tag_name) + if [ -z "$LATEST_RELEASE_TAG" ] || [ "$LATEST_RELEASE_TAG" = "null" ]; then + LATEST_RELEASE_TAG="v1.0.0" + fi + echo "latest_release_tag=$LATEST_RELEASE_TAG" >> "$GITHUB_OUTPUT" + echo "latest_release_version=${LATEST_RELEASE_TAG#v}" >> "$GITHUB_OUTPUT" + + # ------------------------------- + # Sync manifest.json to last release version if behind (only when no meaningful commits) + # ------------------------------- + - name: 🛠 Ensure manifest.json matches latest release version + if: steps.check_commits.outputs.commit_count == '0' + run: | + if [ -f manifest.json ]; then + MANIFEST_VERSION=$(jq -r '.version // empty' manifest.json) + else + MANIFEST_VERSION="" + fi + LATEST_RELEASE_VERSION="${{ steps.get_latest_release.outputs.latest_release_version }}" + PYTHON_CODE="from packaging import version; \ + print(version.parse('$LATEST_RELEASE_VERSION') > version.parse('$MANIFEST_VERSION') if '$MANIFEST_VERSION' else True)" + NEED_UPDATE=$(python3 -c "$PYTHON_CODE") + if [ "$NEED_UPDATE" = "True" ]; then + echo "Updating manifest.json to version $LATEST_RELEASE_VERSION (sync with release)" + jq --arg v "$LATEST_RELEASE_VERSION" '.version = $v' manifest.json > tmp.json && mv tmp.json manifest.json + git config user.name "github-actions" + git config user.email "github-actions@github.com" + git add manifest.json + git commit -m "Sync manifest.json to release $LATEST_RELEASE_VERSION [🔄]" || echo "Nothing to commit" + git push origin main || true + else + echo "Manifest.json is already up-to-date with the latest release." + fi + + # ------------------------------- + # Continue normal workflow if commits exist + # ------------------------------- + - name: 📃 Get list of changed files (Markdown bullet list) + if: steps.check_commits.outputs.commit_count != '0' + id: changed_files + run: | + BULLET_LIST="$(printf '%s\n' "${{ steps.check_commits.outputs.changed_files }}" | sed 's/^/- /')" + echo "CHANGED<> "$GITHUB_OUTPUT" + printf '%s\n' "$BULLET_LIST" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + COUNT="$(printf '%s\n' "${{ steps.check_commits.outputs.changed_files }}" | wc -l)" + echo "COUNT=$COUNT" >> "$GITHUB_OUTPUT" + + - name: Get manifest version + if: steps.check_commits.outputs.commit_count != '0' + id: get_manifest_version + run: | + if [ -f manifest.json ]; then + MANIFEST_VERSION=$(jq -r '.version // empty' manifest.json) + if [ -z "$MANIFEST_VERSION" ] || [ "$MANIFEST_VERSION" = "null" ]; then + MANIFEST_VERSION="1.0.0" + fi + else + MANIFEST_VERSION="1.0.0" + fi + echo "manifest_version=$MANIFEST_VERSION" >> "$GITHUB_OUTPUT" + + - name: Pick base version + if: steps.check_commits.outputs.commit_count != '0' + id: pick_base_version + run: | + LATEST_RELEASE="${{ steps.get_latest_release.outputs.latest_release_version }}" + MANIFEST="${{ steps.get_manifest_version.outputs.manifest_version }}" + BASE_VERSION=$(python3 -c "from packaging import version; \ + print(str(max(version.parse('$LATEST_RELEASE'), version.parse('$MANIFEST'))))") + echo "base_version=$BASE_VERSION" >> "$GITHUB_OUTPUT" + + - name: 🔢 Determine version + if: steps.check_commits.outputs.commit_count != '0' + id: version + run: | + BASE_VERSION="${{ steps.pick_base_version.outputs.base_version }}" + IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION" + COUNT="${{ steps.changed_files.outputs.COUNT }}" + if [ "$COUNT" -ge 5 ]; then + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 + elif [ "$COUNT" -ge 3 ]; then + MINOR=$((MINOR + 1)) + PATCH=0 + else + PATCH=$((PATCH + 1)) + fi + NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" + REPO_NAME="$(basename "$GITHUB_REPOSITORY")" + ZIP_NAME="${REPO_NAME}-${NEW_VERSION}.zip" + echo "VERSION=$NEW_VERSION" >> "$GITHUB_OUTPUT" + echo "ZIP_NAME=$ZIP_NAME" >> "$GITHUB_OUTPUT" + echo "REPO_NAME=$REPO_NAME" >> "$GITHUB_OUTPUT" + + - name: 🛠 Update or create manifest.json + if: steps.check_commits.outputs.commit_count != '0' + run: | + VERSION="${{ steps.version.outputs.VERSION }}" + AUTHOR="Ivan Carlos" + VERSION_FILE="manifest.json" + if [ -f "$VERSION_FILE" ]; then + jq --arg v "$VERSION" --arg a "$AUTHOR" \ + '.version = $v | .author = $a' "$VERSION_FILE" > tmp.json && mv tmp.json "$VERSION_FILE" + else + echo "{ \"version\": \"$VERSION\", \"author\": \"$AUTHOR\" }" > "$VERSION_FILE" + fi + + - name: 💾 Commit and push updated manifest.json + if: steps.check_commits.outputs.commit_count != '0' + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" + git add manifest.json + git commit -m "Update manifest version to ${{ steps.version.outputs.VERSION }} [▶️]" || echo "Nothing to commit" + git push origin main + + - name: 📦 Create ZIP package (excluding certain files) + if: steps.check_commits.outputs.commit_count != '0' + run: | + ZIP_NAME="${{ steps.version.outputs.ZIP_NAME }}" + zip -r "$ZIP_NAME" . -x ".git/*" ".github/*" "docker/*" ".dockerignore" "CNAME" "Dockerfile" "README.md" "LICENSE" + + - name: 🚀 Create GitHub Release + if: steps.check_commits.outputs.commit_count != '0' + uses: softprops/action-gh-release@v2 + with: + tag_name: "v${{ steps.version.outputs.VERSION }}" + name: "${{ steps.version.outputs.REPO_NAME }} v${{ steps.version.outputs.VERSION }}" + body: | + ### Changelog + Files changed in this release: + ${{ steps.changed_files.outputs.CHANGED }} + files: ${{ steps.version.outputs.ZIP_NAME }} + + # ----- Docker steps ----- + - name: 🔍 Check if Dockerfile exists + if: steps.check_commits.outputs.commit_count != '0' + id: dockerfile_check + run: | + if [ -f Dockerfile ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: 🛠 Set up Docker Buildx + if: steps.check_commits.outputs.commit_count != '0' && steps.dockerfile_check.outputs.exists == 'true' + uses: docker/setup-buildx-action@v3 + + - name: 🔐 Login to GitHub Container Registry + if: steps.check_commits.outputs.commit_count != '0' && steps.dockerfile_check.outputs.exists == 'true' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: 🐳 Build and Push Docker image + if: steps.check_commits.outputs.commit_count != '0' && steps.dockerfile_check.outputs.exists == 'true' + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ghcr.io/${{ github.repository }}:latest diff --git a/.github/workflows/update_readme.yml b/.github/workflows/update_readme.yml new file mode 100644 index 0000000..b635451 --- /dev/null +++ b/.github/workflows/update_readme.yml @@ -0,0 +1,78 @@ +name: Update README + +# Allow GitHub Actions to commit and push changes +permissions: + contents: write + +on: + workflow_dispatch: + schedule: + - cron: '0 4 * * *' # Every day at 4 AM UTC + +jobs: + update-readme: + runs-on: ubuntu-latest + + env: + SOURCE_REPO: ivancarlosti/.github + SOURCE_BRANCH: main + + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + + - name: Checkout source README template + uses: actions/checkout@v4 + with: + repository: ${{ env.SOURCE_REPO }} + ref: ${{ env.SOURCE_BRANCH }} + path: source_readme + + - name: Update README.md (buttons and footer) + run: | + set -e + REPO_NAME="${GITHUB_REPOSITORY##*/}" + + # --- Extract buttons block from source --- + BUTTONS=$(awk '//{flag=1;next}//{flag=0}flag' source_readme/README.md) + BUTTONS_UPDATED=$(echo "$BUTTONS" | sed "s/\.github/${REPO_NAME}/g") + + # --- Extract footer block from source (everything from onward) --- + FOOTER=$(awk '//{flag=1}flag' source_readme/README.md) + + # --- Replace buttons section in README.md --- + UPDATED=$(awk -v buttons="$BUTTONS_UPDATED" ' + BEGIN { skip=0 } + // { + print + print buttons + skip=1 + next + } + // && skip { + print + skip=0 + next + } + !skip { print } + ' README.md) + + # --- Replace everything after with FOOTER --- + echo "$UPDATED" | awk -v footer="$FOOTER" ' + // { + print footer + found=1 + exit + } + { print } + ' > README.tmp && mv README.tmp README.md + + - name: Remove source_readme from git index + run: git rm --cached -r source_readme || true + + - name: Commit and push changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + file_pattern: README.md + commit_message: "Sync README from template [▶️]" + branch: ${{ github.ref_name }} diff --git a/ADMIN-install-modules.ps1 b/ADMIN-install-modules.ps1 new file mode 100644 index 0000000..9b9ee91 --- /dev/null +++ b/ADMIN-install-modules.ps1 @@ -0,0 +1,76 @@ + +# Check for administrator privileges +$currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent() +$principal = New-Object Security.Principal.WindowsPrincipal($currentIdentity) +if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Host "⚠ This script must be run as Administrator. Please restart PowerShell with elevated privileges." -ForegroundColor Yellow + Read-Host -Prompt "Press Enter to exit" + exit +} + +# Trust PSGallery if not already trusted +try { + if ((Get-PSRepository -Name "PSGallery").InstallationPolicy -ne "Trusted") { + Set-PSRepository -Name "PSGallery" -InstallationPolicy Trusted + } +} catch { + Write-Warning "Failed to set PSGallery as trusted: $($_.Exception.Message)" +} + +# List of required modules +$modules = @( +    'MicrosoftTeams', +    'ImportExcel', +    'Microsoft.Graph', +    'ExchangeOnlineManagement' +) + + +$updatedModules = @() + +foreach ($module in $modules) { + try { + $installed = Get-InstalledModule -Name $module -ErrorAction Stop + Write-Host "`nUpdating module: $module (Current: $($installed.Version))" -ForegroundColor Cyan + Update-Module -Name $module -Force -ErrorAction Stop + $updatedModules += $module + Write-Host "Updated $module to latest version." -ForegroundColor Green + } catch { + Write-Host "`nModule not found or update failed: $module. Attempting install..." -ForegroundColor Yellow + try { + Install-Module -Name $module -Scope AllUsers -Force -AllowClobber -ErrorAction Stop + Write-Host "Successfully installed $module." -ForegroundColor Green + $updatedModules += $module + } catch { + Write-Warning ("Failed to install {0}: {1}" -f $module, $_.Exception.Message) + } + } +} + +# Ask user if they want to remove old versions +if ($updatedModules.Count -gt 0) { + $response = Read-Host "`nDo you want to remove old versions of the updated modules? (y/n)" + if ($response -match '^(y|yes)$') { + foreach ($module in $updatedModules) { + try { + $allVersions = Get-InstalledModule -Name $module -AllVersions + $latest = $allVersions | Sort-Object Version -Descending | Select-Object -First 1 + $oldVersions = $allVersions | Where-Object { $_.Version -ne $latest.Version } + + foreach ($old in $oldVersions) { + Write-Host "Removing old version $($old.Version) of $module..." -ForegroundColor DarkGray + Uninstall-Module -Name $module -RequiredVersion $old.Version -Force -ErrorAction Stop + } + Write-Host "Cleaned up old versions of $module." -ForegroundColor Green + } catch { + Write-Warning ("Failed to remove old versions of {0}: {1}" -f $module, $_.Exception.Message) + } + } + } else { + Write-Host "Skipping module cleanup." -ForegroundColor Yellow + } +} + +Write-Host "`nAll modules processed successfully." -ForegroundColor Cyan +Read-Host -Prompt "Press Enter to exit" + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3c1c4d9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Ivan Carlos de Almeida + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/POWERSHELL_ISSUE.md b/POWERSHELL_ISSUE.md new file mode 100644 index 0000000..7075b0f --- /dev/null +++ b/POWERSHELL_ISSUE.md @@ -0,0 +1,122 @@ + + +### How to Fix "Running Scripts is Disabled on this System" in PowerShell + +PowerShell is blocking your script due to its execution policy settings, which are in place for security reasons. You can change these settings easily to allow your script to run. + +#### **Recommended Steps** + +1. **Open PowerShell as Administrator** + - Click Start, search for PowerShell. + - Right-click and select **Run as Administrator**. +2. **Check Current Execution Policies** + - Run: + +``` +Get-ExecutionPolicy -List +``` + + - This displays the policies for each scope (see table below for explanations). +3. **Unblock Script Execution for Your User** + - To allow scripts for your user only (safest): + +``` +Set-ExecutionPolicy RemoteSigned -Scope CurrentUser +``` + + - *RemoteSigned* allows scripts you write and scripts downloaded from the internet if they are signed. +4. **If You Need to Allow All Scripts (Less Secure)** + - Run: + +``` +Set-ExecutionPolicy Unrestricted -Scope CurrentUser +``` + + - This will allow any script to run, regardless of source. Use only if you trust the scripts you’re executing. +5. **Confirm Security Prompts** + - If prompted, press **Y** to confirm or type **A** (if asked) to accept all future changes[^1_1][^1_2][^1_3]. + +#### **If You Still Get the Error (Group Policy Enforced)** + +If changing the execution policy doesn’t work, your system might have a Group Policy overriding this setting: + +- Press `Win + R`, type `gpedit.msc`, and press Enter. +- Navigate to: + +``` +Local Computer Policy > Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell +``` + +- Double-click **Turn on Script Execution**. +- Set it to **Enabled** and choose **Allow all scripts** (or less permissive option if desired)[^1_1][^1_4]. + + +#### **Execution Policy Scopes Explained** + +| Scope | Description | +| :-- | :-- | +| MachinePolicy | Enforced by Group Policy for entire machine | +| UserPolicy | Enforced by Group Policy for current user | +| Process | Applies only to current PowerShell session | +| CurrentUser | Applies to scripts run by current Windows user | +| LocalMachine | Applies to all users on the computer | + +*Execution policies set at higher scopes (MachinePolicy, UserPolicy) override lower scopes (CurrentUser, LocalMachine)[^1_5].* + +#### **Quick Troubleshooting** + +- If only running a script once, you can bypass the policy by running: + +``` +powershell -ExecutionPolicy Bypass -File .\mainscript.ps1 +``` + +- Use `RemoteSigned` or `Unrestricted` mindfully, as they lower script execution restrictions. + +**Note:** Always revert your execution policy to the original or a safer state (such as `Restricted`) after running untrusted or experimental scripts for security[^1_6][^1_5]. + +**References:** +Information sourced directly from Microsoft documentation and community troubleshooting discussions[^1_1][^1_2][^1_5][^1_3]. + +
+ +[^1_1]: https://stackoverflow.com/questions/4037939/powershell-says-execution-of-scripts-is-disabled-on-this-system + +[^1_2]: https://www.addictivetips.com/windows-tips/fix-running-scripts-is-disabled-on-this-system-powershell-on-windows-10/ + +[^1_3]: https://techpress.net/powershell-running-scripts-is-disabled-on-this-system-error/ + +[^1_4]: https://learn.microsoft.com/en-us/answers/questions/506985/powershell-execution-setting-is-overridden-by-a-po + +[^1_5]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.5 + +[^1_6]: https://www.youtube.com/watch?v=ChRef6Z8UD4 + +[^1_7]: https://answers.microsoft.com/en-us/windows/forum/all/cannot-get-powershell-script-to-run/900edc39-35e8-4896-92d0-05aad75eac87 + +[^1_8]: https://superuser.com/questions/106360/how-to-enable-execution-of-powershell-scripts + +[^1_9]: https://learn.microsoft.com/en-us/answers/questions/3740158/cannot-get-powershell-script-to-run + +[^1_10]: https://adamtheautomator.com/set-executionpolicy/ + +[^1_11]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy?view=powershell-7.5 + +[^1_12]: https://www.youtube.com/watch?v=N2Axkw00Flg + +[^1_13]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy?view=powershell-7.5\&rut=459b26fec52a14755fbe25de9c676e1e9b897f03730570c69e5b368ad8ae747c + +[^1_14]: https://dev.to/jackfd120/resolving-npm-execution-policy-error-in-powershell-a-step-by-step-guide-for-developers-32ip + +[^1_15]: https://www.softwareverify.com/blog/enabling-and-disabling-powershell-script-execution/ + +[^1_16]: https://learn.microsoft.com/pt-br/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.5 + +[^1_17]: https://lazyadmin.nl/powershell/running-scripts-is-disabled-on-this-system/ + +[^1_18]: https://tecadmin.net/powershell-running-scripts-is-disabled-system/ + +[^1_19]: https://stackoverflow.com/questions/41117421/ps1-cannot-be-loaded-because-running-scripts-is-disabled-on-this-system + +[^1_20]: https://sentry.io/answers/bypass-and-set-powershell-script-execution-policies/ + diff --git a/README.md b/README.md index a680889..8d97626 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,53 @@ -# m365auditor +# Microsoft 365 Auditor script +This script collects users, groups and Teams of a Microsoft 365 environment on .xlsx file for audit and review purposes + +[![Stars](https://img.shields.io/github/stars/ivancarlosti/m365auditor?label=⭐%20Stars&color=gold&style=flat)](https://github.com/ivancarlosti/m365auditor/stargazers) +[![Watchers](https://img.shields.io/github/watchers/ivancarlosti/m365auditor?label=Watchers&style=flat&color=red)](https://github.com/sponsors/ivancarlosti) +[![Forks](https://img.shields.io/github/forks/ivancarlosti/m365auditor?label=Forks&style=flat&color=ff69b4)](https://github.com/sponsors/ivancarlosti) +[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/ivancarlosti/m365auditor?label=Activity)](https://github.com/ivancarlosti/m365auditor/pulse) +[![GitHub Issues](https://img.shields.io/github/issues/ivancarlosti/m365auditor?label=Issues&color=orange)](https://github.com/ivancarlosti/m365auditor/issues) +[![License](https://img.shields.io/github/license/ivancarlosti/m365auditor?label=License)](LICENSE) +[![GitHub last commit](https://img.shields.io/github/last-commit/ivancarlosti/m365auditor?label=Last%20Commit)](https://github.com/ivancarlosti/m365auditor/commits) +[![Security](https://img.shields.io/badge/Security-View%20Here-purple)](https://github.com/ivancarlosti/m365auditor/security) +[![Code of Conduct](https://img.shields.io/badge/Code%20of%20Conduct-2.1-4baaaa)](https://github.com/ivancarlosti/m365auditor?tab=coc-ov-file) +[![GitHub Sponsors](https://img.shields.io/github/sponsors/ivancarlosti?label=GitHub%20Sponsors&color=ffc0cb)][sponsor] + + +## Instructions +* Save the last release version and extract files locally (download [here](https://github.com/ivancarlosti/m365auditor/releases/latest)) +* Change variables of `mainscript.ps1` if needed +* Update `tenantIds.txt` with your tenants +* Run `mainscript.ps1` on PowerShell (right-click on file > Run with PowerShell) +* Follow instructions selecting tenant, authenticate, collect .zip file on `$destinationpath` +* If needs help to install or update required modules, run `ADMIN-install-modules.ps1` as administrator + +## Requirements +* Windows 10+ or Windows Server 2019+ +* PowerShell 5.x (some modules still runs under .NET Framework, PowerShell 7.x uses .NET Core) +* Modules `MicrosoftTeams`, `ImportExcel`, `Microsoft.Graph`, `ImportExcel` on PowerShell + + +--- + +## 🧑‍💻 Consulting and technical support +* For personal support and queries, please submit a new issue to have it addressed. +* For commercial related questions, please [**contact me**][ivancarlos] for consulting costs. + +## 🩷 Project support +| If you found this project helpful, consider | +| :---: | +[**buying me a coffee**][buymeacoffee], [**donate by paypal**][paypal], [**sponsor this project**][sponsor] or just [**leave a star**](../..)⭐ +|Thanks for your support, it is much appreciated!| + +[cc]: https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-code-of-conduct-to-your-project +[contributing]: https://docs.github.com/en/articles/setting-guidelines-for-repository-contributors +[security]: https://docs.github.com/en/code-security/getting-started/adding-a-security-policy-to-your-repository +[support]: https://docs.github.com/en/articles/adding-support-resources-to-your-project +[it]: https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser +[prt]: https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository +[funding]: https://docs.github.com/en/articles/displaying-a-sponsor-button-in-your-repository +[ivancarlos]: https://ivancarlos.it +[buymeacoffee]: https://www.buymeacoffee.com/ivancarlos +[paypal]: https://icc.gg/donate +[sponsor]: https://github.com/sponsors/ivancarlosti diff --git a/mainscript.ps1 b/mainscript.ps1 new file mode 100644 index 0000000..5e9681a --- /dev/null +++ b/mainscript.ps1 @@ -0,0 +1,513 @@ +# Set console output encoding to UTF-8 for Latin characters +[console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# Specify the output directory for working files, report and other variables +$outputDirectory = (New-Object -ComObject Shell.Application).NameSpace('shell:Downloads').Self.Path +$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" + +# Read tenant IDs from a text file +$tenantIdsFilePath = ".\tenantIds.txt" +if (-not (Test-Path $tenantIdsFilePath)) { + Write-Host "Tenant IDs file not found. Please create a file named 'tenantIds.txt' with the tenant IDs, one per line." + pause + exit +} +$tenantIds = Get-Content -Path $tenantIdsFilePath + +# Prompt the user to select a tenant +Write-Host +Write-Host "Select a Tenant ID to connect:" +for ($i = 0; $i -lt $tenantIds.Count; $i++) { + Write-Host "$($i + 1). $($tenantIds[$i])" +} +$tenantSelection = Read-Host "Enter the number of the Tenant ID you want to connect to" +$selectedTenantId = $tenantIds[$tenantSelection - 1] + +# Check if ImportExcel module is installed, if not, ask the user to install it +if (-not (Get-Module -ListAvailable -Name ImportExcel)) { + Write-Host "ImportExcel module is not installed. Please install it using the following command: Install-Module -Name ImportExcel -Force -AllowClobber" + pause + exit +} + +# Check if Microsoft Teams module is installed, if not, ask the user to install it +if (-not (Get-Module -ListAvailable -Name MicrosoftTeams)) { + Write-Host "Microsoft Teams module is not installed. Please install it using the following command: Install-Module -Name MicrosoftTeams -Force -AllowClobber" + pause + exit +} + +# Check if Microsoft Graph module is installed, if not, ask the user to install it +if (-not (Get-Module -ListAvailable -Name Microsoft.Graph)) { + Write-Host "Microsoft Graph module is not installed. Please install it using the following command: Install-Module -Name Microsoft.Graph -Force -AllowClobber" + pause + exit +} + +# Check if Exchange Online module is installed, if not, ask the user to install it +if (-not (Get-Module -ListAvailable -Name ExchangeOnlineManagement)) { + Write-Host "ExchangeOnlineManagement module is not installed. Please install it using the following command: Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber" + pause + exit +} + +# Import the required modules +Import-Module Microsoft.Graph.Authentication +Import-Module Microsoft.Graph.Users # Get-MgUser Get-MgUserLicenseDetail +Import-Module Microsoft.Graph.Groups +Import-Module Microsoft.Graph.Teams # Get-MgTeam +Import-Module Microsoft.Graph.Identity.DirectoryManagement # Get-MgDomain +Import-Module MicrosoftTeams # Get-TeamChannel Get-TeamChannelUser +Import-Module ImportExcel # Export-Excel +Import-Module ExchangeOnlineManagement # Get-Mailbox + +# Connect to Microsoft Graph +Write-Host +Write-Host "Connecting to Microsoft Graph..." +Connect-MgGraph -TenantId $selectedTenantId -Scopes "User.Read.All Directory.Read.All Group.Read.All Team.ReadBasic.All TeamMember.Read.All ChannelMember.Read.All Domain.Read.All" -NoWelcome +Write-Host +Write-Host "Connected to Microsoft Graph." + +# Connect to Microsoft Teams +Write-Host +Write-Host "Connecting to Microsoft Teams..." +Connect-MicrosoftTeams +Write-Host "Connected to Microsoft Teams." + +# Connect to Exchange Online +Write-Host +Write-Host "Connecting to Exchange Online..." +Connect-ExchangeOnline -ShowBanner:$false +Write-Host +Write-Host "Connected to Exchange Online." + +# Create the output directory if it doesn't exist +if (-not (Test-Path $outputDirectory)) { + New-Item -ItemType Directory -Path $outputDirectory +} + + +# Export Groups with all columns available +Write-Host +Write-Host "Exporting groups..." +$groups = Get-MgGroup -All + + +# Export Users with account status and licenses using Graph (temporary) + +# Comprehensive license mapping including Visio SKUs +$licenseMap = @{ + 'a403ebcc-fae0-4ca2-8c8c-7a907fd6c235' = 'Microsoft Fabric (Free)' + 'f245ecc8-75af-4f8e-b61f-27d8114de5f3' = 'Microsoft 365 Business Standard' + '3b555118-da6a-4418-894f-7df1e2096870' = 'Microsoft 365 Business Basic' + 'f30db892-07e9-47e9-837c-80727f46fd3d' = 'Microsoft Power Automate Free' + '4b9405b0-7788-4568-add1-99614e613b69' = 'Exchange Online (Plan 1)' + '19ec0d23-8335-4cbd-94ac-6050e30712fa' = 'Exchange Online (Plan 2)' + 'cdd28e44-67e3-425e-be4c-737fab2899d3' = 'Microsoft 365 Apps for business' + '50509a35-f0bd-4c5e-89ac-22f0e16a00f8' = 'Microsoft Teams Rooms Basic without Audio Conferencing' + '6af4b3d6-14bb-4a2a-960c-6c902aad34f3' = 'Microsoft Teams Rooms Basic' + 'e0dfc8b9-9531-4ec8-94b4-9fec23b05fc8' = 'Microsoft Teams Exploratory Dept (unlisted)' + 'f8a1db68-be16-40ed-86d5-cb42ce701560' = 'Power BI Pro' + 'cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46' = 'Microsoft 365 Business Premium' + 'c1d032e0-5619-4761-9b5c-75b6831e1711' = 'Power BI Premium Per User' + '53818b1b-4a27-454b-8896-0dba576410e6' = 'Planner and Project Plan 3' + '295a8eb0-f78d-45c7-8b5b-1eed5ed02dff' = 'Microsoft Teams Shared Devices' + 'c5928f49-12ba-48f7-ada3-0d743a3601d5' = 'Vision Plan 2' + '1f2f344a-700d-42c9-9427-5cea1d5d7ba6' = 'Microsoft Stream Trial' + '52ea0e27-ae73-4983-a08f-13561ebdb823' = 'Teams Premium (for Departments)' + '639dec6b-bb19-468b-871c-c5c441c4b0cb' = 'Microsoft 365 Copilot' + '5b631642-bd26-49fe-bd20-1daaa972ef80' = 'Microsoft Power Apps for Developer' + 'beb6439c-caad-48d3-bf46-0c82871e12be' = 'Planner Plan 1' + '3f9f06f5-3c31-472c-985f-62d9c10ec167' = 'Power Pages vTrial for Makers' +} + + +# Get users and process license information +$usersGraph = Get-MgUser -All -Property ` + Id,DisplayName,UserPrincipalName,Mail,AccountEnabled,UserType, ` + CreatedDateTime,AssignedLicenses,Department,JobTitle,OfficeLocation, ` + UserPrincipalName,ProxyAddresses + + +$usersGraphReport = $usersGraph | ForEach-Object { + $user = $_ + $skuPartNumbers = @() + + try { + $licenseDetails = Get-MgUserLicenseDetail -UserId $user.Id + foreach ($license in $licenseDetails) { + $skuPartNumbers += $license.SkuPartNumber + } + } catch { + Write-Warning "Could not get license details for $($user.UserPrincipalName)" + $skuPartNumbers += "Error" + } + + [PSCustomObject]@{ + Id = $user.Id + DisplayName = $user.DisplayName + UserPrincipalName = $user.UserPrincipalName + Mail = $user.Mail + AccountEnabled = $user.AccountEnabled + UserType = $user.UserType + Licenses = ($user.AssignedLicenses | ForEach-Object { + if ($licenseMap.ContainsKey($_.SkuId)) { + $licenseMap[$_.SkuId] + } else { + $_.SkuId + } + }) -join ", " + LicenseSKUs = ($user.AssignedLicenses.SkuId -join ", ") + SkuPartNumbers = $skuPartNumbers -join ", " + CreatedDateTime = $user.CreatedDateTime + Department = $user.Department + JobTitle = $user.JobTitle + OfficeLocation = $user.OfficeLocation + } +} + +$usersGraphReport | Export-Csv -Path "$outputDirectory\UsersGraph.csv" -NoTypeInformation -Encoding UTF8 + + +# Export Shared Mailboxes +Write-Host +Write-Host "Exporting Shared Mailboxes..." +$sharedMailboxes = Get-Mailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited | Select-Object DisplayName,PrimarySmtpAddress,ExternalDirectoryObjectId + +$sharedMailboxes | Export-Csv -Path "$outputDirectory\SharedMailboxes.csv" -NoTypeInformation -Encoding UTF8 +Write-Host "Shared Mailboxes exported to $outputDirectory\SharedMailboxes.csv." + + +# Get all Teams +$teams = Get-MgTeam -All + +# Export Teams using original file name +$teams | Select-Object Id, DisplayName, Description, Visibility | ` + Export-Csv -Path "$outputDirectory\Teams.csv" -NoTypeInformation -Encoding UTF8 -Force +Write-Host "Teams exported to $outputDirectory\Teams.csv." + +# Export Teams Channels and Memberships +Write-Host +Write-Host "Exporting Teams channels and memberships..." +$teamsChannels = @() + +# Loop through Teams +foreach ($team in $teams) { + # Check if Team Id is null or empty + if ([string]::IsNullOrEmpty($team.Id)) { + Write-Host "Skipping team '$($team.DisplayName)' because Id is null or empty." + continue + } + + # Get channels for the team + try { + $channels = Get-TeamChannel -GroupId $team.Id + } catch { + Write-Warning "Could not get channels for team $($team.DisplayName): $_" + continue + } + + foreach ($channel in $channels) { + $channelMembers = @() + $membershipNote = "" + + if ($channel.MembershipType -eq "shared") { + $membershipNote = "Shared channel – membership not accessible via Teams module" + Write-Warning "Skipping members of shared channel '$($channel.DisplayName)' in team '$($team.DisplayName)'" + } else { + try { + $channelMembers = Get-TeamChannelUser -GroupId $team.Id -DisplayName $channel.DisplayName + } catch { + $membershipNote = "Error retrieving users: $_" + Write-Warning "Could not get users for channel $($channel.DisplayName) in team $($team.DisplayName): $_" + } + } + + if (-not $channelMembers -or $channelMembers.Count -eq 0) { + $teamsChannels += [PSCustomObject]@{ + TeamName = $team.DisplayName + TeamId = $team.Id + ChannelName = $channel.DisplayName + ChannelId = $channel.Id + MembershipType = $channel.MembershipType + UserName = "" + Role = "" + Notes = $membershipNote + } + } else { + foreach ($member in $channelMembers) { + $teamsChannels += [PSCustomObject]@{ + TeamName = $team.DisplayName + TeamId = $team.Id + ChannelName = $channel.DisplayName + ChannelId = $channel.Id + MembershipType = $channel.MembershipType + UserName = $member.User + Role = $member.Role + Notes = "" + } + } + } + } +} + + +$teamsChannels | Export-Csv -Path "$outputDirectory\TeamsChannels.csv" -NoTypeInformation -Encoding UTF8 -Force +Write-Host "Teams channels and memberships exported to $outputDirectory\TeamsChannels.csv." + +# Wait +Start-Sleep -Seconds 2 + + +# Export Domains +Write-Host +Write-Host "Exporting domains..." +$domains = Get-MgDomain + +$domains | Export-Csv -Path "$outputDirectory\Domains.csv" -NoTypeInformation -Encoding UTF8 +Write-Host "Domains exported to $outputDirectory\Domains.csv." + + +# Convert Teams and Groups data to arrays for easier comparison +$teamsArray = $teams | ForEach-Object { + [PSCustomObject]@{ + GroupId = $_.GroupId + } +} + +$groupsArray = $groups | ForEach-Object { + $aliases = (Get-MgGroup -GroupId $_.Id).ProxyAddresses -join ", " + [PSCustomObject]@{ + ObjectId = $_.Id + DisplayName = $_.DisplayName + MailEnabled = $_.MailEnabled + Mail = $_.Mail + SecurityEnabled = $_.SecurityEnabled + SharePointGroup = if ($teamsArray.GroupId -contains $_.Id -or $aliases -match "SPO:") { "Yes" } else { "No" } + Aliases = $aliases + } +} + +$groupsArray | Export-Csv -Path "$outputDirectory\Groups.csv" -NoTypeInformation -Encoding UTF8 +Write-Host "Groups exported to $outputDirectory\Groups.csv." + +# Export Group Memberships to separate CSV files based on SharePoint group status +Write-Host +Write-Host "Exporting group memberships..." +$sharePointMembershipsCsvPath = "$outputDirectory\SharePointMemberships.csv" +$dlMembershipsCsvPath = "$outputDirectory\DLMemberships.csv" +$sharePointMemberships = @() +$dlMemberships = @() + +foreach ($group in $groupsArray) { + $owners = Get-MgGroupOwner -GroupId $group.ObjectId + $members = Get-MgGroupMember -GroupId $group.ObjectId + + foreach ($owner in $owners) { + $userInfo = $usersGraph | Where-Object { $_.Id -eq $owner.Id } + $membership = [PSCustomObject]@{ + GroupName = $group.DisplayName + GroupObjectId = $group.ObjectId + ObjectId = $owner.Id + DisplayName = $userInfo.DisplayName + UserPrincipalName= $userInfo.UserPrincipalName + MembershipStatus = "Owner" + } + if ($group.SharePointGroup -eq "Yes") { + $sharePointMemberships += $membership + } else { + $dlMemberships += $membership + } + } + + foreach ($member in $members) { + $userInfo = $usersGraph | Where-Object { $_.Id -eq $member.Id } + $membership = [PSCustomObject]@{ + GroupName = $group.DisplayName + GroupObjectId = $group.ObjectId + ObjectId = $member.Id + DisplayName = $userInfo.DisplayName + UserPrincipalName= $userInfo.UserPrincipalName + MembershipStatus = "Member" + } + if ($group.SharePointGroup -eq "Yes") { + $sharePointMemberships += $membership + } else { + $dlMemberships += $membership + } + } +} + +$sharePointMemberships | Export-Csv -Path $sharePointMembershipsCsvPath -NoTypeInformation -Encoding UTF8 +Write-Host "SharePoint memberships exported to $sharePointMembershipsCsvPath." + +$dlMemberships | Export-Csv -Path $dlMembershipsCsvPath -NoTypeInformation -Encoding UTF8 +Write-Host "DL memberships exported to $dlMembershipsCsvPath." + + + + + + +# Get all distribution groups +$groups = Get-DistributionGroup -ResultSize Unlimited + +# Prepare an array to store results +$dlOwnersCsvPath = "$outputDirectory\DLOwners.csv" +$dlOwners = @() + +foreach ($group in $groups) { + foreach ($owner in $group.ManagedBy) { + $ownerRecipient = Get-Recipient -Identity $owner -ErrorAction SilentlyContinue + # Defensive: Only output if we get a valid recipient + if ($ownerRecipient) { + $dlOwners += [PSCustomObject]@{ + GroupDisplayName = $group.DisplayName + GroupPrimarySmtp = $group.PrimarySmtpAddress + OwnerDisplayName = $ownerRecipient.DisplayName + OwnerEmail = $ownerRecipient.PrimarySmtpAddress + } + } + } +} + +# Export to CSV +$dlOwners | Export-Csv -Path $dlOwnersCsvPath -NoTypeInformation -Encoding UTF8 +Write-Host "Distribution list owners exported to $outputDirectory\DLOwners.csv." + + + + + + + +# Disconnect from Microsoft Teams (if connected) +try { + Disconnect-MicrosoftTeams -ErrorAction SilentlyContinue + Write-Host "Disconnected from Microsoft Teams." -ForegroundColor Green +} catch { + Write-Host "No active Teams session to disconnect." -ForegroundColor Yellow +} + +# Disconnect from Microsoft Graph (if connected) +try { + Disconnect-MgGraph -ErrorAction SilentlyContinue + Write-Host "Disconnected from Microsoft Graph." -ForegroundColor Green +} catch { + Write-Host "No active Graph session to disconnect." -ForegroundColor Yellow +} + +# Disconnect from Exchange Online (if connected) +try { + Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue + Write-Host "Disconnected from Exchange Online." -ForegroundColor Green +} catch { + Write-Host "No active Exchange Online session to disconnect." -ForegroundColor Yellow +} + + +# Consolidate all CSVs into an Excel file with each CSV in a separate sheet +Write-Host +Write-Host "Consolidating CSVs into an Excel file..." +$excelFilePath = "$outputDirectory\$selectedTenantId-report_$timestamp.xlsx" + +$excelSheets = @( + @{Name="Users"; Path="$outputDirectory\UsersGraph.csv"}, + @{Name="SharedMailboxes"; Path="$outputDirectory\SharedMailboxes.csv"}, + @{Name="Groups"; Path="$outputDirectory\Groups.csv"}, + @{Name="SharePointMemberships"; Path="$sharePointMembershipsCsvPath"}, + @{Name="DLMemberships"; Path="$dlMembershipsCsvPath"}, + @{Name="DLOwners"; Path="$dlOwnersCsvPath"}, + @{Name="Teams"; Path="$outputDirectory\Teams.csv"}, + @{Name="TeamsChannels"; Path="$outputDirectory\TeamsChannels.csv"}, + @{Name="Domains"; Path="$outputDirectory\Domains.csv"} +) + +foreach ($sheet in $excelSheets) { + Import-Csv -Path $sheet.Path | Export-Excel -Path $excelFilePath -WorksheetName $sheet.Name -AutoSize -TableName $sheet.Name -TableStyle Light1 +} + +Write-Host "CSV consolidation complete. Excel file saved to $excelFilePath." + +# Wait +Start-Sleep -Seconds 2 + + +# Delete the CSV files after consolidation +foreach ($sheet in $excelSheets) { + Remove-Item -Path $sheet.Path -Force + Write-Host "Deleted $($sheet.Path)" +} + +# Clear the prompt +Clear-Host + +# Clear the prompt +cls + +# Calculate MD5 hash of the Excel file +Write-Host "Calculating MD5 hash of the Excel file..." +Write-Host "File name: $selectedTenantId-report_$timestamp.xlsx" +$md5Hash = Get-FileHash -Path $excelFilePath -Algorithm MD5 | Select-Object -ExpandProperty Hash +Write-Host "MD5 hash of the Excel file: $md5Hash" +Write-Host + +# Wait to print info on screen for print screen +Start-Sleep -Seconds 2 + +# Print screen program +Add-Type -AssemblyName System.Windows.Forms +Add-Type -AssemblyName System.Drawing + +# Send alt + printscreen to capture the active window +[System.Windows.Forms.SendKeys]::SendWait("%{PRTSC}") +Start-Sleep -Milliseconds 500 # Give clipboard time to update + +# Try to get image from clipboard +$image = [System.Windows.Forms.Clipboard]::GetImage() +if ($image -ne $null) { + $bitmap = New-Object System.Drawing.Bitmap $image + $screenshotPath = "$outputDirectory\screenshot_$timestamp.png" + $bitmap.Save($screenshotPath) + Write-Host "Screenshot saved to $screenshotPath" +} else { + Write-Host "No image found in clipboard. Screenshot not saved." + $screenshotPath = $null +} + +Write-Host +Start-Sleep -Seconds 2 + +# Archive the Excel file, the script itself, and the screenshot in a zip file +Write-Host "Archiving the Excel file, the script itself, and the screenshot in a zip file..." +$zipFilePath = "$outputDirectory\$selectedTenantId-report_$timestamp.zip" +$scriptPath = $MyInvocation.MyCommand.Path +Write-Host "$zipFilePath" +Write-Host "$scriptPath" +Write-Host "$excelFilePath" +Write-Host + +Start-Sleep -Seconds 2 + +# Prepare list of files to archive +$filesToArchive = @($excelFilePath, $scriptPath) +if ($screenshotPath) { + $filesToArchive += $screenshotPath +} + +Compress-Archive -Path $filesToArchive -DestinationPath $zipFilePath +Write-Host "Files archived to $zipFilePath" + +Start-Sleep -Seconds 2 + +# Delete the Excel file and the screenshot if it exists +Remove-Item -Path $excelFilePath -Force +if ($screenshotPath) { + Remove-Item -Path $screenshotPath -Force +} +Write-Host "Deleted $selectedTenantId-report_$timestamp.xlsx and screenshot (if created)" + +Start-Sleep -Seconds 2 + +# Pause at the end +pause diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..0df78c2 --- /dev/null +++ b/manifest.json @@ -0,0 +1,4 @@ +{ + "version": "1.1.18", + "author": "Ivan Carlos" +} diff --git a/pendings.txt b/pendings.txt new file mode 100644 index 0000000..d7e49db --- /dev/null +++ b/pendings.txt @@ -0,0 +1,11 @@ +ToDo: + +* ADMIN-install-modules.ps1 can warn `'PackageManagement' is currently in use`, it's safe to ignore this warning. +* Find a way to list last user login and activity, last signin call using Microsoft Graph requires AD Premium license. +* Microsoft Graph API workaround Microsoft Graph module limitations but requires app registration. +* Pending to validate Shared channels on Teams if they will add note related to unable to catch users +* Improve Shared Mailbox with members +* Create delegated mailbox TAB +* Improve DLs merging members and owners using ExchangeOnline module, owners list have issue reporting System.Object[] +* List aliases of all mail accounts (users, dls, sharepoint) +* List 2FA settings of Microsoft 365 (portal admins + enforced rule + AzureAD) diff --git a/tenantIds.txt b/tenantIds.txt new file mode 100644 index 0000000..9b35c2c --- /dev/null +++ b/tenantIds.txt @@ -0,0 +1 @@ +tenantID01.onmicrosoft.com tenantID02.onmicrosoft.com tenantID03.onmicrosoft.com \ No newline at end of file diff --git a/testing-guideline_en-US.md b/testing-guideline_en-US.md new file mode 100644 index 0000000..1a1ed46 --- /dev/null +++ b/testing-guideline_en-US.md @@ -0,0 +1,13 @@ +Please validate the Excel (.xlsx) document attached and share your approval. Any necessary adjustments should be responded to in this email, and after the adjustments, a new document will be generated for validation and approval. + +### How to validate users, groups, and access in the organization's technology environment: + +1. **Users Tab**: Check if all users should exist in the environment based on their email (column A) or username (column K). Users who have been terminated can only be present in the list if column "E" is "Disabled", indicating that the user is blocked. Note that users who do not belong to a natural person (such as generic/non-nominal users) should not be maintained. + +2. **SharePointMemberships Tab**: Check if all SharePoints should exist in the environment based on their name (column A). Each user with access to the SharePoint is listed in a new row; verify if the listed user (column D, E, F) should have access to the SharePoint indicated in the same row (column A). Also, check if each user's permission is appropriate for their role (column G). + +3. **DLMemberships Tab**: Check if all Distributiol lists should exist in the environment based on their name (column A). Each user that receive messages from each Distribution List is listed in a new row; verify if the listed user (column D, E, F) should get e-mail messages from related Distribution List indicated in the same row (column A). Also, check if each user's permission is appropriate for their role (column G). + + **Permission Descriptions**: + - **Owner**: Admin permission, has full control over the SharePoint/Distribution List. + - **Member**: Have full control over files/get messages but cannot approve, add or remove new members diff --git a/testing-guideline_pt-BR.md b/testing-guideline_pt-BR.md new file mode 100644 index 0000000..4779790 --- /dev/null +++ b/testing-guideline_pt-BR.md @@ -0,0 +1,13 @@ +Por favor, valide o documento Excel (.xlsx) em anexo e compartilhe sua aprovação. Quaisquer ajustes necessários devem ser respondidos neste e-mail, e após os ajustes, um novo documento será gerado para validação e aprovação. + +### Como validar usuários, grupos e acesso no ambiente tecnológico da organização: + +1. **Aba Users**: Verifique se todos os usuários devem existir no ambiente com base em seu e-mail (coluna A) ou nome de usuário (coluna K). Usuários que foram desligados só podem estar presentes na lista se a coluna "E" estiver "Desativado", indicando que o usuário está bloqueado. Note que usuários que não pertencem a uma pessoa natural (como usuários genéricos/não nominais) não devem ser mantidos. + +2. **Aba SharePointMemberships**: Verifique se todos os SharePoints devem existir no ambiente com base em seu nome (coluna A). Cada usuário com acesso ao SharePoint está listado em uma nova linha; verifique se o usuário listado (coluna D, E, F) deve ter acesso ao SharePoint indicado na mesma linha (coluna A). Além disso, verifique se a permissão de cada usuário é apropriada para seu papel (coluna G). + +3. **Aba DLMemberships**: Verifique se todas as listas de distribuição devem existir no ambiente com base em seu nome (coluna A). Cada usuário que recebe mensagens de cada lista de distribuição está listado em uma nova linha; verifique se o usuário listado (coluna D, E, F) deve receber mensagens de e-mail da lista de distribuição relacionada indicada na mesma linha (coluna A). Além disso, verifique se a permissão de cada usuário é apropriada para seu papel (coluna G). + + **Descrições de Permissão**: + - **Proprietário**: Permissão de administrador, tem controle total sobre o SharePoint/Lista de Distribuição. + - **Membro**: Tem controle total sobre arquivos/receber mensagens, mas não pode aprovar, adicionar ou remover novos membros.