Node.js: alterando o plano de energia do Windows
Olá meus Unicórnios! 🦄✨
Trocar o plano de energia do Windows por código parece a tarefa mais boba do mundo. 😅 Tem um comando pronto que vem com o sistema, ele aceita um argumento, e acabou. É aquele tipo de coisa que você resolve em cinco minutos entre duas tarefas de verdade.
Foi exatamente o que eu pensei. E aí eu fui rodar, conferir a saída, e descobri que duas das minhas suposições estavam erradas — uma delas escrita com todas as letras num comentário do meu próprio código. 😳
Este artigo é o caminho inteiro: ler o plano ativo, listar os planos da máquina, trocar de plano e confirmar que trocou mesmo. Tudo com Node.js puro, sem instalar nada — nem um npm install. E com as armadilhas que só aparecem quando você roda de verdade. ⚡
🔌 O comando que faz tudo
A boa notícia é que o Windows já resolve isso sozinho, e o programa se chama powercfg. Ele está em qualquer instalação, desde sempre, e não precisa de instalação nenhuma. Três subcomandos dão conta do artigo inteiro:
powercfg /getactivescheme :: qual plano esta ativo agora
powercfg /list :: quais planos existem nesta maquina
powercfg /setactive <guid> :: ativa um plano
Rodando o primeiro aqui, a resposta veio assim:
GUID do Esquema de Energia: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (Alto desempenho)
E o /list mostra os três de sempre, com um asterisco marcando o ativo:
Esquemas de Energia Existentes (* Ativos)
-----------------------------------
GUID do Esquema de Energia: 381b4222-f694-41f0-9685-ff5bb260df2e (Equilibrado)
GUID do Esquema de Energia: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (Alto desempenho) *
GUID do Esquema de Energia: a1841308-3541-4fab-bc81-f71556f20b4a (Economia de energia)
Repare no formato, porque ele é o mesmo nos dois comandos e é o que a gente vai extrair com uma expressão regular só: um GUID, espaços, e o nome entre parênteses. 🎯
Uma boa notícia que eu não esperava: /setactive não precisa de administrador. Rodei num terminal comum e o plano mudou. Isso muda bastante o desenho de quem quer chamar isso de dentro de um serviço.
🆔 Por que GUID, e não o nome do plano
Essa é a decisão mais importante do artigo, e a mais fácil de errar. A tentação é escrever assim:
// NAO faca isso
powercfg('/setactive', 'Economia de energia'); // ❌
Não funciona — o /setactive só aceita GUID. Mas o problema real é mais fundo: mesmo que você leia o /list e procure a linha cujo nome seja "Economia de energia", você acabou de escrever um código que só funciona em máquina com Windows em português. 🌍
No Windows em inglês, aquela mesma linha diz "Power saver". Em espanhol, "Economizador de energía". O seu if não acha nada, o script diz "plano não encontrado" e você vai passar a tarde procurando bug no lugar errado.
A saída é que os três planos padrão do Windows têm GUIDs canônicos — o mesmo número em toda instalação do mundo, em qualquer idioma:
const PLANOS = {
economia: {
guid: 'a1841308-3541-4fab-bc81-f71556f20b4a',
nomes: ['Economia de energia', 'Power saver'],
},
equilibrado: {
guid: '381b4222-f694-41f0-9685-ff5bb260df2e',
nomes: ['Equilibrado', 'Balanced'],
},
desempenho: {
guid: '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c',
nomes: ['Alto desempenho', 'High performance'],
},
};
Compare com a saída lá de cima: os três GUIDs batem exatamente. Esses números são estáveis e você pode gravá-los no código sem medo. ✅
🕳️ E quando o GUID canônico não existe na máquina?
Aqui está o motivo de aquela lista ter um campo nomes que eu ainda não usei. Se o GUID é universal, para que guardar nome?
Porque o plano pode não estar lá. 😱 Isso acontece mais do que parece:
- Notebook de fabricante. Muita máquina sai de fábrica com planos próprios ("Modo silencioso", "Alto desempenho turbo"), com GUID gerado na hora, e alguns fabricantes removem os originais.
- Imagem corporativa. Política de grupo apaga os planos padrão e coloca um plano único, controlado pelo TI.
- Plano clonado. Quem cria um plano a partir de outro (
powercfg /duplicatescheme) ganha um plano com o nome parecido e GUID novo.
Então a busca tem dois passos, nessa ordem: tenta o GUID canônico; se ele não estiver na lista da máquina, aí sim procura por nome — em português e em inglês:
function resolverGuid(chave) {
const info = PLANOS[chave];
if (!info) return null;
const planos = listarPlanos();
// 1) o canonico, que vale em qualquer idioma
if (planos.some((p) => p.guid.toLowerCase() === info.guid.toLowerCase())) {
return info.guid;
}
// 2) so entao o nome, para maquina com planos proprios
for (const p of planos) {
const bate = info.nomes.some(
(alvo) => p.nome.toLowerCase().indexOf(alvo.toLowerCase()) !== -1
);
if (bate) return p.guid;
}
return null; // desistiu, e isso e uma resposta valida
}
A ordem importa e não é intercambiável. Se o nome viesse primeiro, uma máquina com um plano chamado "Alto desempenho turbo" (do fabricante) faria o script ativar o plano do fabricante em vez do plano padrão do Windows — mesmo com o padrão instalado e disponível ali do lado. O GUID é preciso; o nome é o chute educado. 🎯
Repare também no indexOf(...) !== -1 em vez de igualdade: é ele que faz "Economia de energia (Fabricante)" casar com "Economia de energia". Comparação exata perderia todos os planos clonados.
E repare no return null do fim. Não encontrar é um resultado legítimo, não um caso impossível — numa máquina corporativa travada, é o resultado esperado. Quem chama precisa tratar isso, e a gente vai tratar já já.
Como eu não tenho aqui um notebook de fabricante nem uma máquina em inglês, testei essa função contra listas montadas à mão, com os três cenários:
fabricante economia -> 11111111-1111-1111-1111-111111111111
fabricante desempenho -> 22222222-2222-2222-2222-222222222222
ingles economia -> a1841308-3541-4fab-bc81-f71556f20b4a
travada economia -> null
Os três caminhos funcionam: casa por nome quando o GUID sumiu, casa por GUID quando o Windows está em inglês, e devolve null — sem explodir — quando não há nada parecido. 🧪
🔤 A armadilha do acento: o TextDecoder que não conhece a codepage
Se você só for ler uma seção deste artigo, leia esta. 🙏 Foi aqui que eu descobri que estava errada.
A saída do powercfg em português tem acento, e acento em saída de programa de console do Windows é sempre uma novela. Eu sabia disso, e tinha escrito no meu código a solução que todo mundo escreve:
// O que eu tinha escrito — e que esta ERRADO
const buf = execFileSync('powercfg', args, { windowsHide: true });
return new TextDecoder('windows-1252').decode(buf);
Faz todo sentido, né? Windows, português, windows-1252. Eu tinha até um comentário orgulhoso ao lado explicando que era por causa dos acentos.
Só que o console do Windows não fala windows-1252. 🤯 Ele fala a codepage OEM, que num Windows pt-BR é a 850. E são coisas diferentes de verdade. Fui olhar os bytes crus de uma mensagem de erro do próprio powercfg:
bytes : 506172836d6574726f7320696e76a06c69646f73
P a r ?? m e t r o s i n v ?? l i d o s
O byte 0x83 está onde deveria estar um â, e o 0xa0 onde deveria estar um á. Agora veja o que cada decodificador faz com esses mesmos bytes:
windows-1252 "Parƒmetros inv lidos -- tente digitar \"/?\" para obter ajuda"
utf-8 "Par�metros inv�lidos -- tente digitar \"/?\" para obter ajuda"
ibm850 NAO SUPORTADO pelo TextDecoder
cp850 NAO SUPORTADO pelo TextDecoder
Olha o tamanho da pegadinha. 😖 O windows-1252 não dá erro nenhum — ele decodifica felizinho e entrega "Parƒmetros". Em CP1252 o byte 0x83 realmente é ƒ e o 0xa0 é um espaço rígido. A conversão foi tecnicamente bem-sucedida e o resultado é lixo. É pior que quebrar: quebrar avisa.
E a solução óbvia — trocar por 'cp850' — não existe. O TextDecoder do Node implementa a lista da especificação Encoding, e as codepages OEM da IBM não estão nela. Ele lança na hora de criar o decodificador.
A saída que eu achei mais limpa é não brigar com a decodificação e sim pedir ao Windows que fale UTF-8 desde o começo. Isso é o chcp 65001, e ele precisa rodar no mesmo processo do powercfg — por isso a chamada passa pelo cmd /c, com os dois comandos encadeados:
function powercfg(args) {
const linha = 'chcp 65001 >nul && powercfg ' + args.join(' ');
const buf = execFileSync('cmd', ['/c', linha], { windowsHide: true });
return buf.toString('utf8');
}
O >nul engole o "Página de código ativa: 65001" que o chcp imprime, senão ele entraria no meio da sua saída. E o resultado, nos mesmos bytes de antes:
utf8 : "Parâmetros inválidos -- tente digitar \"/?\" para obter ajuda"
utf8 : "GUID de Subgrupos: 0012ee47-9041-4b5d-9b77-535fba8b1442 (Disco rígido)"
Acento certo, sem caractere de substituição, sem tabela de conversão na mão. 🎉
🎭 A segunda coisa que eu achava e estava errada
Eu tinha escrito no código, com toda a confiança:
// O powercfg /setactive nao imprime nada e sai com codigo 0 mesmo quando
// o GUID nao vale. Reler e a unica prova de que o plano mudou de verdade.
Parece o tipo de coisa que a gente aprende apanhando, né? Pois é. Fui provar, mandando um GUID que não existe:
--- /setactive com GUID inexistente ---
Parametros invalidos -- tente digitar "/?" para obter ajuda
lancou. status = 1
Ele lança. 😅 O execFileSync vê o código de saída 1 e joga a exceção direitinho, com a mensagem de erro no stderr. Metade do meu comentário era invenção minha — provavelmente lembrança de outro comando que se comporta assim.
Achei importante contar isso em vez de apagar e fingir que sempre soube, por dois motivos. O primeiro: "escrevi um comentário defensivo" não é o mesmo que "testei". Comentário sobre comportamento de programa externo envelhece, e às vezes já nasce errado.
O segundo é que a conclusão do comentário continua certa, mesmo com a premissa furada. Reler depois de escrever continua valendo a pena — só que por outro motivo: não para pegar erro silencioso do powercfg, e sim porque o plano de energia é estado compartilhado da máquina. O usuário pode clicar no ícone da bateria no mesmo segundo; uma política de grupo pode reverter; outro programa pode estar fazendo a mesma coisa. Reler é barato e transforma "eu mandei trocar" em "está trocado":
powercfg(['/setactive', guid]);
// Reler e barato e e a unica prova de que o plano mudou mesmo.
const depois = planoAtivo();
if (depois.guid.toLowerCase() !== guid.toLowerCase()) {
throw new Error('o plano nao mudou: continua em "' + depois.nome + '"');
}
return { antes, depois };
Devolver { antes, depois } em vez de true é o detalhe que faz esse código ser bom de usar por outro programa: quem chama consegue registrar em log o que mudou, e consegue desfazer mais tarde, porque sabe de onde veio. Foi assim que eu devolvi esta máquina ao plano original no fim dos testes. 🔙
🧩 O script inteiro
Junta tudo: leitura, listagem, resolução de GUID com fallback, escrita com confirmação, e uma CLI simples em cima. Sem dependência nenhuma — é só salvar como plano.js e rodar.
/**
* plano.js — le e altera o plano de energia do Windows com Node.js.
*
* Sem dependencia nenhuma: so o powercfg, que ja vem no Windows.
*
* node plano.js mostra o plano ativo
* node plano.js listar lista os planos desta maquina
* node plano.js economia ativa Economia de energia
* node plano.js equilibrado ativa Equilibrado
* node plano.js desempenho ativa Alto desempenho
*/
'use strict';
const { execFileSync } = require('child_process');
// GUIDs canonicos dos tres planos do Windows. Sao os mesmos em qualquer
// instalacao e em qualquer idioma: e por isso que a busca comeca por GUID,
// e o nome so entra como plano B.
const PLANOS = {
economia: {
guid: 'a1841308-3541-4fab-bc81-f71556f20b4a',
nomes: ['Economia de energia', 'Power saver'],
},
equilibrado: {
guid: '381b4222-f694-41f0-9685-ff5bb260df2e',
nomes: ['Equilibrado', 'Balanced'],
},
desempenho: {
guid: '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c',
nomes: ['Alto desempenho', 'High performance'],
},
};
/**
* Roda o powercfg e devolve a saida como texto.
*
* O `chcp 65001` nao e enfeite: o powercfg em portugues escreve na codepage
* do console (850 nesta maquina), e o TextDecoder do Node nao conhece CP850.
* Sem isso, "Disco rigido" volta com o acento quebrado. Mandar o Windows
* falar UTF-8 antes resolve na origem.
*/
function powercfg(args) {
const linha = 'chcp 65001 >nul && powercfg ' + args.join(' ');
const buf = execFileSync('cmd', ['/c', linha], { windowsHide: true });
return buf.toString('utf8');
}
// Toda linha de plano tem o mesmo formato: <guid> (Nome do plano)
const LINHA = /([0-9a-fA-F-]{36})\s*\((.+?)\)/;
/** LEITURA: o plano ativo agora, como { guid, nome, chave }. */
function planoAtivo() {
const saida = powercfg(['/getactivescheme']);
const m = saida.match(LINHA);
if (!m) return { guid: '', nome: saida.trim(), chave: null };
const ativo = { guid: m[1], nome: m[2].trim(), chave: null };
// Descobre se o plano ativo e um dos tres conhecidos. O GUID decide;
// o nome so e consultado para planos clonados, que tem GUID proprio.
for (const chave of Object.keys(PLANOS)) {
const info = PLANOS[chave];
const mesmoGuid = ativo.guid.toLowerCase() === info.guid.toLowerCase();
const mesmoNome = info.nomes.some(
(n) => ativo.nome.toLowerCase().indexOf(n.toLowerCase()) !== -1
);
if (mesmoGuid || mesmoNome) {
ativo.chave = chave;
break;
}
}
return ativo;
}
/** LEITURA: todos os planos desta maquina, como [{ guid, nome }]. */
function listarPlanos() {
const planos = [];
for (const linha of powercfg(['/list']).split(/\r?\n/)) {
const m = linha.match(LINHA);
if (m) planos.push({ guid: m[1], nome: m[2].trim() });
}
return planos;
}
/**
* Descobre qual GUID ativar para uma chave.
*
* O canonico vem primeiro. Se ele nao estiver na maquina — imagem
* corporativa que apagou os planos, notebook cujo fabricante substituiu
* tudo por planos proprios — cai para o nome, em portugues e em ingles.
*/
function resolverGuid(chave) {
const info = PLANOS[chave];
if (!info) return null;
const planos = listarPlanos();
if (planos.some((p) => p.guid.toLowerCase() === info.guid.toLowerCase())) {
return info.guid;
}
for (const p of planos) {
const bate = info.nomes.some(
(alvo) => p.nome.toLowerCase().indexOf(alvo.toLowerCase()) !== -1
);
if (bate) return p.guid;
}
return null;
}
/** ESCRITA: ativa o plano e RELE para confirmar. Retorna { antes, depois }. */
function ativarPlano(chave) {
if (!PLANOS[chave]) throw new Error('plano invalido: ' + chave);
const antes = planoAtivo();
const guid = resolverGuid(chave);
if (!guid) throw new Error("plano '" + chave + "' nao existe nesta maquina");
powercfg(['/setactive', guid]);
// Reler e barato e e a unica prova de que o plano mudou mesmo. Sem isso,
// qualquer falha silenciosa vira um "pronto!" mentiroso no log.
const depois = planoAtivo();
if (depois.guid.toLowerCase() !== guid.toLowerCase()) {
throw new Error('o plano nao mudou: continua em "' + depois.nome + '"');
}
return { antes, depois };
}
function main() {
const acao = (process.argv[2] || 'status').toLowerCase();
try {
if (acao === 'status') {
const p = planoAtivo();
console.log('Plano ativo: ' + p.nome + ' [' + p.guid + '] chave=' + p.chave);
return;
}
if (acao === 'listar') {
for (const p of listarPlanos()) console.log(p.guid + ' ' + p.nome);
return;
}
if (PLANOS[acao]) {
const r = ativarPlano(acao);
console.log('Antes: ' + r.antes.nome + ' [' + r.antes.guid + ']');
console.log('Depois: ' + r.depois.nome + ' [' + r.depois.guid + ']');
return;
}
console.log('Uso: node plano.js [status|listar|economia|equilibrado|desempenho]');
process.exitCode = 1;
} catch (e) {
console.error('ERRO: ' + e.message);
process.exitCode = 1;
}
}
if (require.main === module) main();
module.exports = { planoAtivo, listarPlanos, resolverGuid, ativarPlano, PLANOS };
Dois detalhes de forma que valem o comentário:
O if (require.main === module) antes do main() é o que permite que este arquivo seja as duas coisas: um comando de terminal e um módulo. Sem ele, um require('./plano') feito de outro script imprimiria o texto de uso da CLI no meio da sua aplicação. 🙃
E o execFileSync, não o execSync: o argumento vai como array, sem passar pelo interpretador de linha de comando, o que dispensa pensar em aspas e escape. Aqui os argumentos são GUIDs que a gente mesmo escreveu, então o risco é baixo — mas é o hábito certo para quando o argumento vier de fora.
✅ Rodando de ponta a ponta
A execução real, na sequência: ler o plano, listar, ir para economia, ir para equilibrado, tentar um plano que não existe, e voltar ao original.
=== status ===
Plano ativo: Alto desempenho [8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c] chave=desempenho
=== listar ===
381b4222-f694-41f0-9685-ff5bb260df2e Equilibrado
8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c Alto desempenho
a1841308-3541-4fab-bc81-f71556f20b4a Economia de energia
=== economia ===
Antes: Alto desempenho [8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c]
Depois: Economia de energia [a1841308-3541-4fab-bc81-f71556f20b4a]
=== equilibrado ===
Antes: Economia de energia [a1841308-3541-4fab-bc81-f71556f20b4a]
Depois: Equilibrado [381b4222-f694-41f0-9685-ff5bb260df2e]
=== turbo (invalido) ===
Uso: node plano.js [status|listar|economia|equilibrado|desempenho]
exit=1
=== RESTAURA desempenho ===
Antes: Equilibrado [381b4222-f694-41f0-9685-ff5bb260df2e]
Depois: Alto desempenho [8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c]
E a conferência final, feita pelo próprio Windows e não pelo meu script — porque conferir o próprio trabalho com a própria ferramenta não prova grande coisa:
> powercfg /getactivescheme
GUID do Esquema de Energia: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (Alto desempenho)
Repare no exit=1 do caminho de erro. Um script que vai ser chamado por agendador ou por outro programa precisa sair com código diferente de zero quando falha — senão quem chamou acha que deu tudo certo. É o que o process.exitCode = 1 do catch garante. 🚦
E sim: a máquina voltou para o plano em que estava antes de eu começar. Ler o estado antes de mexer e restaurar no fim é uma gentileza básica de quem escreve código que altera configuração de sistema. 💙
📌 O que eu levo daqui
O comando era mesmo de cinco minutos. O que consumiu a tarde foram as duas suposições — e as duas estavam escritas em comentários meus, com ar de quem sabia o que estava dizendo.
Uma delas me fazia decodificar a saída na codepage errada, silenciosamente. A outra atribuía ao powercfg um defeito que ele não tem. Nenhuma das duas apareceria sem rodar o programa e olhar os bytes. 🔍
Por hoje é só, meus unicórnios! 🦄✨
Que a magia do arco-íris continue brilhando em suas vidas! Até mais! 🌈🌟
Leia também
Node.js: exportando grandes volumes do Elasticsearch
Como exportar milhões de registros do Elasticsearch para CSV com Node.js: a parede dos 10.000, a Scroll API e o scroll que fica aberto.
Node.js: gerando certificado SSL e instalando no IIS
Como emitir um certificado gratuito do Let's Encrypt com Node.js e instalar no IIS do Windows, com as armadilhas que travaram tudo.
Servidor RTMP que grava e apaga sozinho
Como receber video de cameras por RTMP, gravar em arquivos pequenos e apagar os antigos sozinho, usando o MediaMTX e um pouco de Node.js.