Skip to content

Commit

Permalink
Merge pull request #3 from LarryWisherMan/feature-PublicTests
Browse files Browse the repository at this point in the history
Refactor Profile Management Functions and Introduce Enhanced Error Handling*
  • Loading branch information
LarryWisherMan authored Sep 12, 2024
2 parents b5b8137 + 422fe89 commit bd3f8f3
Show file tree
Hide file tree
Showing 31 changed files with 2,595 additions and 316 deletions.
122 changes: 113 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,120 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Get-UserProfileFolders`
- `Get-RegistryUserProfiles`
- `Get-UserFolders`
- `Get-SIDProfileInfo`

- Comment-based help documentation added for the following public functions:
- `New-UserProfileObject`
- `Remove-RegistryKeyForSID`
- `Remove-ProfilesForSIDs`
- `Get-RegistryKeyForSID`
- `Get-ProfilePathFromSID`
- `Test-FolderExists`
- `Test-OrphanedProfile`
- `Test-SpecialAccount`
- `New-UserProfileObject`
- `Remove-RegistryKeyForSID`
- `Remove-ProfilesForSIDs`
- `Get-RegistryKeyForSID`
- `Get-SIDProfileInfo`
- `Get-ProfilePathFromSID`
- `Test-FolderExists`
- `Test-OrphanedProfile`
- `Test-SpecialAccount`

- Implemented and completed Unit Tests for private functions

- **`Get-UserFolders`**
- Added error handling using a `try`/`catch` block to ensure that if `Get-ChildItem`
fails (e.g., due to permission issues), the function logs an error message and
returns an empty array instead of throwing an unhandled exception.

- Implemented an `OutputType` attribute for better PowerShell function introspection
and to clearly indicate that the function returns an array of `[PSCustomObject]`.

- **`Invoke-UserProfileAudit` Supporting Functions:**

- These supporting functions are now utilized within `Invoke-UserProfileAudit`
to audit user profiles from both the file system and registry sources.

- **`Process-RegistryProfiles`**:
- Processes profiles retrieved from the registry,
compares them with folder profiles, and identifies orphaned profiles.

- **`Process-FolderProfiles`**:
- Processes user profiles from the folder system,
identifies those without corresponding registry entries, and marks them as orphaned.

- **`Test-ComputerReachability`**:
- Encapsulates the common behavior of `Test-ComputerPing` to check if a computer
is reachable before proceeding with operations like profile audits. This ensures
consistent handling of unreachable computers across different functions.

### Changed

- Moved `Get-SIDProfileInfo` to the private functions folder. It will serve as
an internal function for `Get-RegistryUserProfiles`

- **`Get-SIDProfileInfo`**
- Returns an empty array `@()` when no registry
key or SIDs are found, improving handling for cases where there are no profiles.

- Improved error handling to ensure proper error messages when the registry key
or subkeys cannot be opened.

- Enhanced handling of SIDs that are invalid or missing a `ProfileImagePath`,
logging appropriate warnings or verbose messages.

- Optimized function behavior to handle scenarios with no SIDs, invalid SID formats,
and missing `ProfileImagePath` values gracefully.


- **`Get-UserFolders`**
- The function now logs errors when folder retrieval fails, improving diagnostic
feedback.

- The default value for the `ComputerName` parameter is set to `$env:COMPUTERNAME`,
ensuring local computer behavior by default without requiring the user to
specify it manually.

- Refined the `Get-DirectoryPath` call to ensure path conversion consistency
across local and remote environments.

- General code clean-up and improved resilience, returning an empty array when
no folders are found or in case of failure, rather than proceeding
without valid data.

- **`Get-UserProfilesFromRegistry`**
- Added error handling using a `try-catch` block to capture and log errors
during the retrieval of registry profiles.

- Implemented a check using `Test-ComputerPing` to verify if the target computer
is online or reachable before attempting to retrieve registry profiles.

- Returns an empty array `@()` when the target computer is offline or unreachable,
logging a warning in such cases.

- Returns an empty array `@()` when an error occurs while accessing the registry
profiles, logging an error message.

- Integrated with the `-ErrorAction Stop` parameter when calling `Get-SIDProfileInfo`,
ensuring that errors are caught and handled appropriately in the calling function.

- **`Get-UserProfilesFromFolders`**
- Added error handling using a `try-catch` block to capture and log errors
during the retrieval of user profile folders.

- Implemented a check using `Test-ComputerPing` to verify if the target
computer is online or reachable before attempting to retrieve user folders.

- Returns an empty array `@()` when the target computer is offline or
unreachable, logging a warning in such cases.

- Returns an empty array `@()` when an error occurs while accessing the user
folders, logging an error message.


- **`Invoke-UserProfileAudit`**
- Renamed the previous `Get-AllUserProfiles` function to `Invoke-UserProfileAudit`.
- Added `Get-AllUserProfiles` as an alias for `Invoke-UserProfileAudit`
to maintain backward compatibility.

### Removed

- Removed the old `Get-AllUserProfiles` function and replaced it with the new
`Invoke-UserProfileAudit` function.

- Temporarily Removed functions (`Remove-OrphanedProfiles` and
`Remove-ProfilesForSIDs`) related to Removing Users Folders and registry
keys for further testing before implementing
74 changes: 48 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@

# WinProfileOps

<p align="center">
<img src="https://raw.githubusercontent.com/LarryWisherMan/ModuleIcons/main/WinProfileOps.png"
<img src="https://raw.githubusercontent.com/LarryWisherMan/ModuleIcons/main/WinProfileOps.png"
alt="WinProfileOps Icon" width="400" />
</p>

The **WinProfileOps** module provides a robust toolkit for managing Windows user
profiles on both local and remote computers. This module simplifies and automates
complex profile management tasks, such as detecting orphaned profiles, validating
profile paths, and removing stale or corrupted profiles. It handles both filesystem
profile paths, and identifying stale or corrupted profiles. It handles both filesystem
and registry operations, utilizing the **WinRegOps** module for registry-related
functions.

**WinProfileOps** seamlessly integrates with **WinRegOps** to manage profiles by
querying, validating, and deleting user profile-related data from the Windows
querying, validating, and auditing user profile-related data from the Windows
registry. This module is ideal for system administrators who want to streamline
profile management operations, especially in environments with numerous users and
computers.
Expand All @@ -24,7 +23,7 @@ computers.
## Dependencies

- **WinRegOps**: The **WinProfileOps** module depends on
[**WinRegOps**](https://github.com/LarryWisherMan/WinRegOps) for registry
[**WinRegOps**](https://github.com/LarryWisherMan/WinRegOps) for registry
operations such as querying, opening, and modifying registry keys related to user
profiles.

Expand All @@ -36,7 +35,6 @@ computers.
(local and remote).
- **Detect orphaned profiles**, such as profiles missing from the file system or
registry.
- **Remove orphaned or unused profiles** from the system safely.
- **Filter and exclude special accounts** like system or service accounts (e.g.,
`defaultuser0`, `S-1-5-18`).
- **Remote profile management** with support for handling user profiles across
Expand All @@ -51,8 +49,6 @@ computers.

- **Cleaning up orphaned profiles** after system migrations, user deactivations, or
profile corruption.
- **Automating stale profile removal** on both local and remote systems to save disk
space and improve performance.
- **Managing user profiles in large-scale environments**, such as terminal servers,
Citrix environments, or multi-user systems.
- **Excluding system accounts** from profile cleanup operations to prevent accidental
Expand All @@ -75,7 +71,7 @@ You have two options to install **WinProfileOps**:
Install-Module -Name WinProfileOps
```

1. **Install from GitHub Releases**
2. **Install from GitHub Releases**
You can also download the latest release from the
[GitHub Releases page](https://github.com/LarryWisherMan/WinProfileOps/releases).
Download the `.zip` file, extract it, and place it in one of your `$PSModulePath`
Expand All @@ -96,17 +92,16 @@ $orphanedProfiles = Get-OrphanedProfiles -ComputerName "RemotePC" -IgnoreSpecial

This retrieves all orphaned profiles on `RemotePC`, excluding special accounts.

#### Example 2: Removing Orphaned Profiles
#### Example 2: Retrieving User Profiles from the File System

The `Remove-OrphanedProfiles` function allows you to remove orphaned profiles from
a system:
Use the `Get-UserProfilesFromFolders` function to retrieve user profile folders from
the file system on a local or remote machine:

```powershell
Remove-OrphanedProfiles -ComputerName "RemotePC" -WhatIf
$userFolders = Get-UserProfilesFromFolders -ComputerName "Server01"
```

This will show what would happen if the orphaned profiles on `RemotePC` were
deleted, without performing the deletion.
This retrieves user profile folders from the default `C:\Users` directory on `Server01`.

#### Example 3: Retrieving User Profiles from the Registry

Expand All @@ -119,33 +114,60 @@ $registryProfiles = Get-UserProfilesFromRegistry -ComputerName "LocalHost"

This retrieves user profiles from the registry on `LocalHost`.

#### Example 4: Removing a Specific Profile
#### Example 4: Auditing User Profiles

You can remove a specific profile from the registry using `Remove-SIDProfile`:
Use the `Invoke-UserProfileAudit` function to audit profiles across the file system and
registry:

```powershell
Remove-SIDProfile -SID "S-1-5-21-123456789-1001" -ComputerName "Server01"
$allProfiles = Invoke-UserProfileAudit -ComputerName "Server01"
```

This removes the registry key for the profile associated with the specified SID on
`Server01`.
This audits user profiles on `Server01`, returning both file system and registry
profile information.

---

## Key Functions

- **`Get-OrphanedProfiles`**: Detects orphaned profiles by checking both the
registry and file system.
- **`Remove-OrphanedProfiles`**: Safely removes orphaned profiles, with support for
`-WhatIf` and `-Confirm`.
- **`Invoke-UserProfileAudit`**: Audits and compares profiles from both the registry
and file system, identifying discrepancies such as orphaned profiles.
- **`Get-UserProfilesFromRegistry`**: Retrieves user profiles from the Windows
registry.
- **`Get-UserProfilesFromFolders`**: Retrieves user profile folders from the file
system.
- **`Remove-SIDProfile`**: Removes a user profile from the registry based on the
SID.
- **`Test-SpecialAccount`**: Checks if a user profile is considered special or
system-related.

---

## Upcoming Features

### `Remove-UserProfile` (Coming Soon!)

The `Remove-UserProfile` function will provide the ability to remove user
profiles safely from both the registry and the file system. Here are some of the
key features being tested for this functionality:

- **Safely remove user profiles** either from the file system (i.e., user profile
folders) or from the Windows registry.

- **Flexible inputs**: Accepts a UserProfile object, a username, or a SID for
profile removal.

- **Powerful safeguards**: Uses `ShouldProcess`, `-WhatIf`, and `-Confirm` to
ensure that deletion is intentional and carefully reviewed.

- **Handles special accounts**: Prevents accidental removal of critical system
or service accounts (e.g., `S-1-5-18`).

- **Remote profile removal**: Enables profile deletion from both local and remote
computers.

- **Verbose and error handling**: Logs every action taken and handles errors to
provide full transparency and avoid unexpected issues.

The feature is being heavily tested to ensure safety, reliability, and accuracy.

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ function Remove-OrphanedProfiles
# Step 4: Return the results of the removal process
return $removalResults
}



File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function Is-SpecialProfile
{
param (
[string]$FolderName,
[string]$SID,
[string]$ProfilePath
)

return Test-SpecialAccount -FolderName $FolderName -SID $SID -ProfilePath $ProfilePath
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
function Remove-UserFolder
{
param (
[PSCustomObject]$ProfileDetails,
[switch]$Force
)

if ($PSCmdlet.ShouldProcess($ProfileDetails.ProfilePath, "Remove user profile folder"))
{
try
{
if (Test-Path -Path $ProfileDetails.ProfilePath)
{
Remove-Item -Path $ProfileDetails.ProfilePath -Recurse -Force -Confirm:$Force
return [ProfileDeletionResult]::new($ProfileDetails.SID, $ProfileDetails.ProfilePath, $true, "Profile folder removed successfully.", $env:COMPUTERNAME)
}
else
{
Write-Warning "Profile folder not found: $($ProfileDetails.ProfilePath)"
return [ProfileDeletionResult]::new($ProfileDetails.SID, $ProfileDetails.ProfilePath, $false, "Profile folder not found.", $env:COMPUTERNAME)
}
}
catch
{
Write-Error "Error removing profile folder: $_"
return [ProfileDeletionResult]::new($ProfileDetails.SID, $ProfileDetails.ProfilePath, $false, "Error removing profile folder: $_", $env:COMPUTERNAME)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
function Resolve-ProfileDetails
{
param (
[PSCustomObject]$UserProfile,
[string]$UserName,
[string]$SID
)

if ($UserProfile)
{
return [PSCustomObject]@{
SID = $UserProfile.SID
ProfilePath = $UserProfile.ProfilePath
FolderName = Split-Path -Path $UserProfile.ProfilePath -Leaf
}
}
elseif ($UserName)
{
$sid = (Get-WmiObject -Class Win32_UserAccount -Filter "Name='$UserName'" | Select-Object -ExpandProperty SID)
$profilePath = [System.IO.Path]::Combine("$env:SystemDrive\Users", $UserName)
return [PSCustomObject]@{
SID = $sid
ProfilePath = $profilePath
FolderName = $UserName
}
}
elseif ($SID)
{
$profilePath = (Get-WmiObject -Class Win32_UserProfile -Filter "SID='$SID'" | Select-Object -ExpandProperty LocalPath)
return [PSCustomObject]@{
SID = $SID
ProfilePath = $profilePath
FolderName = Split-Path -Path $profilePath -Leaf
}
}
return $null
}
Loading

0 comments on commit bd3f8f3

Please sign in to comment.