# ripbi
[](https://github.com/bgarcevic/ripbi/actions/workflows/ci.yml)
[](https://crates.io/crates/ripbi)
Static analysis, linting, and tree-shaking for Power BI semantic models and DAX.
Docs: [bgarcevic.github.io/ripbi](https://bgarcevic.github.io/ripbi/).
I created ripbi because I liked Measure Killer, but I was looking for an
agent-friendly, free, fast tool to scan semantic models and the connected
reports to identify potentially unused semantic model objects. I tested it on a
shared semantic model with 14 connected reports, and it's 99% faster than
Measure Killer, reducing processing time from almost 4 minutes to a couple of
seconds.
It works without opening the reports or semantic models, so it can run as part
of a CI pipeline. It's also cross-platform, with no dependency on Power BI
Desktop or similar.
It currently works only on local PBIP projects — a TMDL semantic model plus
PBIR reports; `.pbix` and `.pbit` files are not supported yet. I plan to
implement guided automated cleanup, a UI, and tenant scanning.
## Install
macOS and Linux:
```sh
Windows (PowerShell):
```powershell
Both scripts verify the download against the release's sha256 checksums and
install the binary into `~/.local/bin` as both `ripbi` and its short alias
`rib` — the two names are the same tool, so `rib scan` works anywhere
`ripbi scan` does. Or `cargo install ripbi`.
<details>
<summary>Pinning a version, reviewing the scripts first, building from source</summary>
Pin a version:
```sh
```powershell
$env:RIPBI_VERSION = 'v0.2.2'; irm https://raw.githubusercontent.com/bgarcevic/ripbi/main/install.ps1 | iex
```
Both scripts are plain shell and PowerShell. Download them first if you would
rather read before running:
```sh
curl -fsSL https://raw.githubusercontent.com/bgarcevic/ripbi/main/install.sh -o install.sh
sh install.sh
```
```powershell
irm https://raw.githubusercontent.com/bgarcevic/ripbi/main/install.ps1 -OutFile install.ps1
powershell -ExecutionPolicy Bypass -File .\install.ps1 # the flag is only needed if your policy blocks script files
```
From a clone of this repository:
```sh
cargo install --path crates/ripbi-cli
```
</details>
## Updating
Installed with one of the scripts above? `ripbi update` downloads the latest
release, verifies its sha256 checksum, and replaces `ripbi` and `rib` in place:
```sh
ripbi update # update in place; exits 0 when done
ripbi update --check # report only: exits 1 when a newer release exists
```
Exit codes: `0` updated or up to date, `1` update available (only from
`--check`), `2` error (network, checksum, unsupported platform).
Every command checks for a new release at most once a day and prints a dim
one-line notice when one is available. That check is a plain `GET` of the
public release metadata — no data is sent — and `RIPBI_NO_UPDATE_CHECK=1`
disables it. It is also skipped automatically when stderr is not a terminal
(pipes, CI logs), when `CI` is set, or under `-q`.
Cargo-installed copies are never self-replaced: `ripbi update` prints the
`cargo install ripbi --force` command instead. A source build found under a
`target/` directory prints the install-script and `cargo install --path`
alternatives.
On Windows a running executable cannot be deleted, so a completed update may
leave `ripbi.exe.old` (or `rib.exe.old`) beside the new binary. It is safe to
delete, and the next update removes it.
## Quickstart
Clone the repository to get the sample projects, then scan one:
```sh
git clone https://github.com/bgarcevic/ripbi.git
cd ripbi
ripbi scan "samples/AdventureWorks Sales.pbip"
```
Output from the committed AdventureWorks sample (trimmed):
```text
130 objects, 74 reachable from 51 roots, 56 unused
Measures (11)
'Sales'[Average Sales per Order]
← nothing references it
Columns (28)
'Customer'[City]
← only used by hierarchy 'Customer'[Geography] — hierarchy level (also unused)
…
```
The summary line counts the model's objects, how many the reports reach, and
how many are unused. Each finding says why it is dead: nothing references it,
or its only consumer is itself unused.
`ripbi scan` discovers the project itself: pass a `.pbip` file, a project
folder, a `.SemanticModel`, or a `.Report`, or nothing to scan the current
directory.
To scan one named model against every report bound to it, pass `--model`; each
`--report` folder is searched recursively for reports bound to that model:
```sh
ripbi scan --model "samples/AdventureWorks Sales.SemanticModel" --report samples/
```
Exit codes:
| `0` | Nothing unused |
| `1` | Unused objects found |
| `2` | Error (bad path, ingestion failure, …) |
```sh
ripbi scan -q # no output; exit code only
```
## Output
The full output contract (human, `--summary`, `--plain`, `--json`, and
`ripbi.toml` configuration) is documented in the user guide's
[scan chapter](https://bgarcevic.github.io/ripbi/output.html).
## Contributing
Dev setup, workflow, and where things live are documented in
[CONTRIBUTING.md](CONTRIBUTING.md). The short version — CI is the definition
of done, and this is what CI runs:
```sh
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --all-features --locked
cargo test --workspace
```
## License
Dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE).
The `samples/` are Microsoft's own sample projects (MIT) and are not covered by
ripbi's license.